Quantcast
Channel: IT瘾开源推荐
Viewing all 265 articles
Browse latest View live

开源地理位置数据库:tile38

$
0
0

Tile38是地理定位数据存储,空间索引和实时地理围栏。它支持多种对象类型,包括纬度/经度点,边界框,XYZ平铺,Geohashes和GeoJSON。

主要特性:

优点:

  • 兼容 redis 协议
  • 功能函数丰富,比如超时设置, hook 调用通知, json 接口等
  • golang 语言编写, 部署方便
  • 支持主从, 以 redis 的 aof 格式存储数据
  • 搜索函数比起 redis 丰富了很多

缺点:

  • 不是特别稳定, github 还有很多未解决的 issue
  • 没有配置文件, 都在代码里控制, 不清楚作者有没有这方面的打算

其他数据库:

参考链接:


Spring Retry框架——看这篇就够了 - MarvelCode - OSCHINA - 中文开源技术交流社区

$
0
0

简介

    软件架构从当初的单机,演变到后来的集群,再到后来的分布式应用。原本看似可以信任的服务调用,加上了网络因素就变得不再可靠。再考虑到一些调用链路的特殊性,又要保证性能,又要尽可能增加成功率,所以调用方必须肩负起重试的责任。

 

自己写,怎样实现?

    重试并不复杂,首先来分析下重试的调用场景,可以想到业务当中不止一处会需要重试能力,并且业务其实更关乎自己的代码块被重试就可以了,而不在乎如何实现的重试。

    所以变化的是一段可以重复执行的代码块,以及重试次数等。

① 代码块可以用Java8支持的函数式编程解决

② 次数可以用入参/配置实现

    首先定义一个执行的模版,结合上面我们的分析,这个模版需要一个待实行的 方法块,以及 配置。(次数等区分场景,用枚举定义,方便全局管控):

@AllArgsConstructor
@Getter
public enum RetrySceneEnums {

    QUERY_USER_INFO("查询用户信息", 2),
    ;

    private String desc;

    private int retryTimes;
}
abstract class MyRetryTemplate<Req, Resp> {
    /** 配置 */
    private IntegrationRetryEnums retryEnum;
    /** 方法块 */
    private Function<Req, Resp> fuction;

    MyRetryTemplate(IntegrationRetryEnums retryEnum, Function<Req, Resp> fuction) {
        this.retryEnum = retryEnum;
        this.fuction = fuction;
    }

    /**
     * 构建函数
     */
    private Supplier<Function<Req, Resp>> retry = () -> (param) -> {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        return execute(param, 1, stopWatch);
    };

    private Resp execute(Req param, int index, StopWatch stopWatch) {
        if (index > retryEnum.getRetryTimes()) {
            stopWatch.split();
            // 最大的重试次数后仍失败,可以打印摘要日志
            return null;
        }
        try {
            // 执行方法块
            Resp result = fuction.apply(param);
            stopWatch.split();
            // 认为成功
            return result;
        } catch (Throwable e) {
            // 递归重试
            return execute(param, ++index, stopWatch);
        }
    }

    Function<Req, Resp> getRetryService() {
        return retry.get();
    }
}

    逻辑很简单,利用 递归思想(当然也可以用循环),默认调用方法抛出异常才会重试,成功则返回,否则递归直到最大次数,使用 StopWatch 统计总耗时,业务可以打印摘要日志并配置监控等,代码简短,但基本实现了功能。

    对 抛出异常才会重试稍微解释下,为什么不支持对业务成功/失败进行重试。首先重试框架感知不到具体的业务响应结构,进而也无法判断业务的成功与失败,当然重试框架也可以自定义一个响应体并强制使用,但这便于业务代码形成耦合。再退一步讲,也没有必要对业务失败进行重试,因为框架毕竟是框架,只需要做通用的事情,业务的失败到底需不需要重试,需要看具体的场景。

    模板完工了,看下具体怎样改造一个已有的方法,使其支持重试功能,假设当前有一个查询用户信息的方法 UserService#queryById(String id)。需要在 Spring 启动后将其包装以支持重试,通过事件机制监听 ContextRefreshedEvent 事件。

@Component
public class RetryServiceFactory implements ApplicationListener<ContextRefreshedEvent> {

    private static AtomicBoolean INIT = new AtomicBoolean(false);

    @Autowired
    private UserService userService;

    private Function<String, WorkOrderDTO> uerQueryFunction;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        if (INIT.compareAndSet(false, true)) {
            this.uerQueryFunction = new MyRetryTemplate<String, WorkOrderDTO>(QUERY_USER_INFO, userService::queryById) {
            }.getRetryService();
        }
    }

    public Function<String, WorkOrderDTO> getUserQueryService() {
        return uerQueryFunction;
    }
}

    需要使用此能力的地方,这么调用即可:

@Autowired
private RetryServiceFactory retryServiceFactory;

public void businessLogic(String userId){

    // 调用工厂获取自带重试功能的服务,并执行
    UserInfo userInfo = retryServiceFactory.getUserQueryService().apply(userId);
}

    到此,自己手写的重试工具已经完成,如果执行时遇到网络抖动,超时等,都会进行最大2次的重试(次数由 RetrySceneEnums 设置)。

 

    这时候再回头审视下代码,可以发现诸多局限性:

  • 比如 Function 的使用,就限定了入参只能是一个,且必须有返回(即不支持 void);
  • 每次接入一个服务,就要在 RetryServiceFactory 扩充;
  • 考虑网络抖动可能有区间性,盲目连续请求可能全部失败,如要实现等待一段实现再重试,怎么办?
  • 假使底层服务真的挂了,但上层业务系统依旧重试,重试每次都以超时告终(占用线程资源),流量上来以后,必然会牵连业务系统也不可用。自然想到熔断,那重试如何加入断路器(Circuit Breaker)模式?

    遇到共性问题,首先要想到有没有现成的框架可以使用,而不是造轮子。目前我已知的重试框架,有 spring-retry以及  guava-retrying,接下来将对 spring-retry的使用和原理进行讲解。

 

Spring Retry 使用

    同spring事务框架一样,retry框架同样支持编程式和声明式两种。仅需要一个maven依赖。如果使用声明式,需要额外引入 aspectj 。

<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.3.0</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.2</version><scope>runtime</scope></dependency>

编程式

    先来介绍下 编程式使用到的几个主要组件:

  • 重试模板类:org.springframework.retry.support.RetryTemplate
  • 重试策略类:org.springframework.retry.RetryPolicy
  • 重试回退策略(两次重试间等待策略):org.springframework.retry.backoff.BackOffPolicy
  • 重试上下文:org.springframework.retry.RetryContext
  • 监听器:org.springframework.retry.RetryListener

   接下来来看下如何使用,然后穿插讲下不同策略的区别。

    首先是开启重试,并声明一个模板bean。因为只是模板,所以可以借助 Spring IOC 声明为单例。

@Configuration
@EnableRetry(proxyTargetClass = true)
public class RetryConfig {

    @Bean
    public RetryTemplate simpleRetryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();
        // 最大重试次数策略
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(3);
        retryTemplate.setRetryPolicy(simpleRetryPolicy);
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        // 每隔1s后再重试
        fixedBackOffPolicy.setBackOffPeriod(1000);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
        return retryTemplate;
    }

}

    例子代码使用了 SimpleRetryPolicy 和 FixedBackOffPolicy,当然还有其他重试策略和回退策略。枚举如下:

重试策略
策略类效果关键参数
MaxAttemptsRetryPolicy设置最大的重试次数,超过之后执行recovermaxAttempts:最大重试次数
BinaryExceptionClassifierRetryPolicy可以指定哪些异常需要重试,哪些异常不需要重试exceptionClassifier:BinaryExceptionClassifier,异常识别类,其实就是存在一个Map映射,Map<Class<? extends Throwable>, Boolean>,value为true的话就说明需要重试。
SimpleRetryPolicy上面两者的结合,支持次数和自定义异常重试maxAttempts 和 exceptionClassifier
TimeoutRetryPolicy在指定的一段时间内重试timeout:单位ms
ExceptionClassifierRetryPolicy

支持异常和重试策略的映射,比如异常A对应重试策略A,异常B对应重试策略B

exceptionClassifier:本质就是异常和重试策略的映射
CompositeRetryPolicy策略类的组合类,支持两种模式,乐观模式:只要一个重试策略满足即执行,悲观模式:另一种是所有策略模式满足再执行policies:策略数组
optimistic:true-乐观;false-悲观
CircuitBreakerRetryPolicy熔断器模式delegate:策略代理类
openTimeout:[0,openTimeout]会一直执行代理类策略,(openTimeout, resetTimeout] 会半打开,如果代理类判断不可以重试,就会熔断,执行recover逻辑,如果代理类判断还可以重试且重试成功,开关会闭合。
resetTimeout:超过指定时间,开关重置闭合
ExpressionRetryPolicy符合表达式就重试expression:表达式,见 org.springframework.expression.Expression实现
AlwaysRetryPolicy一直重试 
NeverRetryPolicy从不重试 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

回退策略
策略类效果关键参数
FixedBackOffPolicy间隔固定时间重试sleeper:支持线程sleep和Object#wait,区别在于是否释放锁
backOffPeriod:等待间隔
NoBackOffPolicy无等待直接重试 
UniformRandomBackOffPolicy在一个设置的时间区间内。随机等待后重试minBackOffPeriod:区间下限
maxBackOffPeriod:区间上限
sleeper:同上
ExponentialBackOffPolicy在一个设置的时间区间内,等待时长为上一次时长的递增initialInterval:默认起始等待时间
maxInterval:最大等待时间
multiplier:递增倍数
sleeper:同上
ExponentialRandomBackOffPolicy在 ExponentialBackOffPolicy 基础上,乘数随机 

    至此,完成了重试模板的定义。接下来是需要在具体的业务场景下调用模板方法即可。同样的,为了保证复用性,仍然借助函数式编程定义:

@Service
public class RetryTemplateService {

    @Autowired
    private RetryTemplate simpleRetryTemplate;

    public <R, T> R retry(Function<T, R> method, T param) throws Throwable {
        return simpleRetryTemplate.execute(new RetryCallback<R, Throwable>() {
            @Override
            public R doWithRetry(RetryContext context) throws Throwable {
                return method.apply(param);
            }
        }, new RecoveryCallback<R>() {
            @Override
            public R recover(RetryContext context) throws Exception {
                return null;
            }
        });
    }
}
@Service
public class UserService {

    @Autowired
    private RetryTemplateService retryTemplateService;

    public User queryById(String id) {
        // TODO 业务逻辑
    }

    public User queryByIdWithRetry(String id) throws Throwable {
        return retryTemplateService.retry(this::queryById, id);
    }
}

    通过调用  org.springframework.retry.support.RetryTemplate#execute,指定需要支持重试的业务代码回调  org.springframework.retry.RetryCallback,以及全部重试失败的兜底回调  org.springframework.retry.RecoveryCallback

     RetryTemplate 支持四种重载的 execute方法,这里不全部展开分析,大体十分相似,变化的是策略(见上面表格),以及 RetryState(有无状态,下面分析)。

声明式

    通过上面的编码式,还需要针对不同场景编写一到多套模板方法,那有没有什么可以简化这一步呢,毕竟业务没必要关注这些具体的模板代码。就像事务一样,只需要方法上加  @Transactional就可以了。当然,spring-retry 也支持,直接上代码:

@Service
public class UserService {

    @Retryable(include = RetryException.class, exclude = IllegalArgumentException.class, 
      listeners = {"defaultRetryListener"}, backoff = @Backoff(delay = 1000, maxDelay = 2000))
    public User queryById(String id) {
        // TODO 业务逻辑
    }

    @Recover
    public User recover(RetryException e, String id) {
        User user = new User();
        user.setName("兜底");
        return user;
    }
}
@Service
public class DefaultRetryListener implements RetryListener {

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        System.out.println("==前置回调==");
        return true;
    }

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("==后置回调==");
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("==执行报错==");
    }
}

    这样,直接调用 UserService#queryById就支持重试了,比编程式节省了不少工作量。

    我这里一股脑设置了很多注解参数,其实都对应上面编程式的策略,毕竟编程式有的,我声明式也得要。使用时按需即可,接下来对照着 @Retryable中定义的所有属性,解释下含义:

  • recover:指定兜底/补偿的方法名。如果不指定,默认对照 @Recover标识的,第一入参为重试异常,其余入参和出参一致的方法;
  • interceptor:指定方法切面 bean, org.aopalliance.intercept.MethodInterceptor实现类
  • value / include:两者用途一致,指出哪些类型需要重试;
  • exclude:和 include 相反,指出哪些异常不需要重试;
  • label:可以指定唯一标签,用于统计;
  • stateful:默认false。重试是否是有状态的;
  • maxAttempts:最大的重试次数;
  • backoff:指定 @Backoff,回退策略;
  • listeners:指定  org.springframework.retry.RetryListener实现 bean。

    对照着上面编程式,可以看出回退策略用注解 @Backoff支持了。这里还补充了上面编程式没有用到的  RetryListener,它定义了3个方法:

<T,EextendsThrowable>booleanopen(RetryContextcontext,RetryCallback<T,E> callback);
<T,EextendsThrowable>voidclose(RetryContextcontext,RetryCallback<T,E> callback,Throwablethrowable);
<T,EextendsThrowable>voidonError(RetryContextcontext,RetryCallback<T,E> callback,Throwablethrowable);

    open 和 close 分别在重试整体的前置和后置被回调一次,onError 则是重试整体过程中,每次异常都会被回调。(具体细节见下面  源码分析

     @Backoff 的属性如下,基本都对应上面编程式的几种策略,仅简单解释下

  • value / delay:两者都标识延迟时间,为 0则对应 NoBackOffPolicy 策略。
  • maxDelay:最大延迟时间
  • multiplier:递增乘数
  • random:递增乘数是否随机

    重试模式中的断路器,由于其场景的特殊性以及属性的复杂性,则被单独定义成了一个注解 @CircuitBreaker,从注释的定义可以看出,它是在 @Retryable基础之上的扩展。

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(stateful = true)
public @interface CircuitBreaker {
   ....
}

    其中属性的定义,也是在 @Retryable的属性基础上,额外扩充了断路器特有的几个属性,对应上面重试策略表格中—— CircuitBreakerRetryPolicy的关键参数:

  • resetTimeout:重置闭合超时时间
  • openTImeout:半打开状态超时时间

 

源码解析

    那具体 spring-retry 的原理是怎样的呢?是递归还是循环,注解又是怎样实现的?带着这些问题开始源码分析,顺带着可以看下上面没有讲到的 org.springframework.retry.RetryContext 组件在业务中如何使用。 

    首先从模板方法源码开始:

public class RetryTemplate implements RetryOperations {

	@Override
	public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
			RecoveryCallback<T> recoveryCallback) throws E {
		return doExecute(retryCallback, recoveryCallback, null);
	}

    protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
                                                   RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {

        // 通过 set方法设置的重试策略,默认 SimpleRetryPolicy
        RetryPolicy retryPolicy = this.retryPolicy;
        // 通过 set方法设置的回退策略,默认 NoBackOffPolicy
        BackOffPolicy backOffPolicy = this.backOffPolicy;

        // 无状态的:重试策略自定义 org.springframework.retry.RetryPolicy.open
        // 有状态的:根据策略,每次新建/从缓存获取
        RetryContext context = open(retryPolicy, state);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("RetryContext retrieved: " + context);
        }
        // 绑定到 ThreadLocal 以便线程内全局获取
        RetrySynchronizationManager.register(context);

        Throwable lastException = null;

        boolean exhausted = false;
        try {
            // 回调所有的 org.springframework.retry.RetryListener.open
            boolean running = doOpenInterceptors(retryCallback, context);
            // 任意一个监听器的open返回 false则抛出异常
            if (!running) {
                throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
            }

            // Get or Start the backoff context...
            BackOffContext backOffContext = null;
            Object resource = context.getAttribute("backOffContext");

            if (resource instanceof BackOffContext) {
                backOffContext = (BackOffContext) resource;
            }

            if (backOffContext == null) {
                // 回调重试策略的 start 方法
                backOffContext = backOffPolicy.start(context);
                if (backOffContext != null) {
                    context.setAttribute("backOffContext", backOffContext);
                }
            }

            // 回调 org.springframework.retry.RetryPolicy.canRetry 判断是否可以重试
            // 同时可以调用 org.springframework.retry.RetryContext.setExhaustedOnly进行干预,不再进行重试
            while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

                try {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Retry: count=" + context.getRetryCount());
                    }
                    lastException = null;
                    // 回调业务代码块
                    return retryCallback.doWithRetry(context);
                } catch (Throwable e) {
                    lastException = e;
                    try {
                        // 回调 org.springframework.retry.RetryPolicy.registerThrowable
                        registerThrowable(retryPolicy, state, context, e);
                    } catch (Exception ex) {
                        throw new TerminatedRetryException("Could not register throwable", ex);
                    } finally {
                        // 回调 org.springframework.retry.RetryListener.onError
                        doOnErrorInterceptors(retryCallback, context, e);
                    }
                    // 同上面的判断一样,允许重试的话,会回调回退策略
                    if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
                        try {
                            // 这一步会根据策略进行等待/立即执行
                            backOffPolicy.backOff(backOffContext);
                        } catch (BackOffInterruptedException ex) {
                            lastException = e;
                            if (this.logger.isDebugEnabled()) {
                                this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
                            }
                            throw ex;
                        }
                    }

                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Checking for rethrow: count=" + context.getRetryCount());
                    }
                    // 如果指定了 org.springframework.retry.RetryState,会判断是否针对该异常进行抛出,即进行重试阻断
                    if (shouldRethrow(retryPolicy, context, state)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
                        }
                        throw RetryTemplate.<E>wrapIfNecessary(e);
                    }

                }

                if (state != null && context.hasAttribute(GLOBAL_STATE)) {
                    break;
                }
            }

            if (state == null && this.logger.isDebugEnabled()) {
                this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
            }

            exhausted = true;
            // 回调兜底/补偿 org.springframework.retry.RecoveryCallback.recover
            return handleRetryExhausted(recoveryCallback, context, state);

        } catch (Throwable e) {
            throw org.springframework.retry.support.RetryTemplate.<E>wrapIfNecessary(e);
        } finally {
            // 回调 org.springframework.retry.RetryPolicy.close
            close(retryPolicy, context, state, lastException == null || exhausted);
            // 回调 org.springframework.retry.RetryListener.close
            doCloseInterceptors(retryCallback, context, lastException);
            // 清除 ThreadLoacal中存储的 org.springframework.retry.RetryContext
            RetrySynchronizationManager.clear();
        }

    }
}

    整体模板方法不是很复杂,上面注释也标明具体回调哪些方法。大体逻辑就是:

初始化上下文并绑定 -> 回调监听器open判断是否可以重试 -> 回调重试策略start方法 -> 通过重试策略判断是否可以重试 -> 可以的话执行业务代码块 -> 下面两个分支 成功/异常
成功:回调重试策略的close -> 回调监听器的close -> 清除上下文              链路①
异常:回调重试策略的registerThrowable -> 回调监听器的onError -> 回调回退策略的backoff -> 如果设置了 Retrystate,判断是否需要抛异常阻断重试 -> 兜底/补偿逻辑 -> 链路①

    大体流程了解后,先来看下上下文接口的定义:

public interface RetryContext extends AttributeAccessor {

    String NAME = "context.name";  // 上下文的自定义名称

    String STATE_KEY = "context.state";  // RetryState定义的key

    String CLOSED = "context.closed"; // 标识重试是否close

    String RECOVERED = "context.recovered";  // 标识是否执行了兜底/补偿

    String EXHAUSTED = "context.exhausted";  // 标识重试最大次数仍失败

	void setExhaustedOnly();

	boolean isExhaustedOnly();

	RetryContext getParent();

	int getRetryCount();

	Throwable getLastThrowable();
}
public interface AttributeAccessor {
    void setAttribute(String name, @Nullable Object value);

    @Nullable
    Object getAttribute(String name);

    @Nullable
    Object removeAttribute(String name);

    boolean hasAttribute(String name);

    String[] attributeNames();
}

    加上继承类 AttributeAccessor支持的key-value存储,作为上下文,贯穿重试框架的一次调用。框架定义的key常量接口内定义了部分,还有一些分散在实现类以及策略类中。如果业务所需,也可以放置数据到上下文,在一次重试策略调用周期内共享。

    那就来看下上下文的初始化逻辑:

public class RetryTemplate implements RetryOperations {

    protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
        // 如果是无状态的,每次都调用 org.springframework.retry.RetryPolicy.open创建
        if (state == null) {
            return doOpenInternal(retryPolicy);
        }

        Object key = state.getKey();
        // 如果有状态,但设置了强制刷新,同样 org.springframework.retry.RetryPolicy.open创建,并写入 state.key
        if (state.isForceRefresh()) {
            return doOpenInternal(retryPolicy, state);
        }
        // 尝试从缓存获取,不存在走新建
        if (!this.retryContextCache.containsKey(key)) {
            // The cache is only used if there is a failure.
            return doOpenInternal(retryPolicy, state);
        }
        // 缓存获取
        RetryContext context = this.retryContextCache.get(key);
        if (context == null) {
            // 异常场景
            if (this.retryContextCache.containsKey(key)) {
                throw new RetryException("Inconsistent state for failed item: no history found. "
                        + "Consider whether equals() or hashCode() for the item might be inconsistent, "
                        + "or if you need to supply a better ItemKeyGenerator");
            }
            // 因为整体没有锁,所以有这一步作为补偿(没仔细琢磨,会不会有并发逻辑)
            return doOpenInternal(retryPolicy, state);
        }

        // 因为是缓存,所以同一个 state.key 会共享这个上下文,清除会影响其他正在校验这几个key的地方
        context.removeAttribute(RetryContext.CLOSED);
        context.removeAttribute(RetryContext.EXHAUSTED);
        context.removeAttribute(RetryContext.RECOVERED);
        return context;

    }
}

    从这里可以看出,如果个别场景想要在多次调用间共享 RetryContext,就需要定义 state.key,且设置 forceRefresh=false。每次重试独占上下文的话,要么就使用无状态的重试,要么就设置 forceRefresh=true。

    到此为止,基本线索都集中到 RetryPolicy的实现上了,基本都是回调 RetryPolicy 定义的方法。话不多说,来看两个重试策略的实现。

public class SimpleRetryPolicy implements RetryPolicy {

    private volatile int maxAttempts;

    private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);

    public SimpleRetryPolicy(int maxAttempts, BinaryExceptionClassifier classifier) {
        super();
        this.maxAttempts = maxAttempts;
        this.retryableClassifier = classifier;
    }

    @Override
    public boolean canRetry(RetryContext context) {
        // 获取最新一次重试的异常
        Throwable t = context.getLastThrowable();
        // 允许重试的异常 并且 次数<最大重试次数
        return (t == null || retryForException(t)) && context.getRetryCount() < this.maxAttempts;
    }

    @Override
    public void close(RetryContext status) {
    }

    @Override
    public void registerThrowable(RetryContext context, Throwable throwable) {
        SimpleRetryContext simpleContext = ((SimpleRetryContext) context);
        // 记录最近一次异常,并自增重试次数
        simpleContext.registerThrowable(throwable);
    }

    @Override
    public RetryContext open(RetryContext parent) {
        return new SimpleRetryContext(parent);
    }

    private static class SimpleRetryContext extends RetryContextSupport {

        public SimpleRetryContext(RetryContext parent) {
            super(parent);
        }

    }

    private boolean retryForException(Throwable ex) {
        return this.retryableClassifier.classify(ex);
    }

}
public class CircuitBreakerRetryPolicy implements RetryPolicy {

    public static final String CIRCUIT_OPEN = "circuit.open";

    public static final String CIRCUIT_SHORT_COUNT = "circuit.shortCount";

    private static Log logger = LogFactory.getLog(CircuitBreakerRetryPolicy.class);

    private final RetryPolicy delegate;

    private long resetTimeout = 20000;

    private long openTimeout = 5000;

    public CircuitBreakerRetryPolicy(RetryPolicy delegate) {
        this.delegate = delegate;
    }

    public void setResetTimeout(long timeout) {
        this.resetTimeout = timeout;
    }

    public void setOpenTimeout(long timeout) {
        this.openTimeout = timeout;
    }

    @Override
    public boolean canRetry(RetryContext context) {
        CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
        // 判断断路器开关是否打开
        if (circuit.isOpen()) {
            // 打开则不允许重试
            circuit.incrementShortCircuitCount();
            return false;
        } else {
            circuit.reset();
        }
        // 断路器开关闭合 or 半打开,是否允许重试交给代理实现
        return this.delegate.canRetry(circuit.context);
    }

    @Override
    public RetryContext open(RetryContext parent) {
        return new CircuitBreakerRetryContext(parent, this.delegate, this.resetTimeout, this.openTimeout);
    }

    @Override
    public void close(RetryContext context) {
        CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
        // 代理
        this.delegate.close(circuit.context);
    }

    @Override
    public void registerThrowable(RetryContext context, Throwable throwable) {
        CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
        circuit.registerThrowable(throwable);
        // 代理
        this.delegate.registerThrowable(circuit.context, throwable);
    }

    static class CircuitBreakerRetryContext extends RetryContextSupport {

        private volatile RetryContext context;

        private final RetryPolicy policy;

        private volatile long start = System.currentTimeMillis();

        private final long timeout;

        private final long openWindow;

        private final AtomicInteger shortCircuitCount = new AtomicInteger();

        public CircuitBreakerRetryContext(RetryContext parent, RetryPolicy policy, long timeout, long openWindow) {
            super(parent);
            this.policy = policy;
            this.timeout = timeout;
            this.openWindow = openWindow;
            this.context = createDelegateContext(policy, parent);
            setAttribute("state.global", true);
        }

        public void reset() {
            shortCircuitCount.set(0);
            setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
        }

        public void incrementShortCircuitCount() {
            shortCircuitCount.incrementAndGet();
            setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
        }

        private RetryContext createDelegateContext(RetryPolicy policy, RetryContext parent) {
            RetryContext context = policy.open(parent);
            reset();
            return context;
        }

        /* 判断断路器开关是否打开  **/
        public boolean isOpen() {
            // 计算时间间隔
            long time = System.currentTimeMillis() - this.start;
            // 判断是否允许重试
            boolean retryable = this.policy.canRetry(this.context);
            if (!retryable) {
                // 闭合重置开关超时时间
                if (time > this.timeout) {
                    logger.trace("Closing");
                    // 重建上下文
                    this.context = createDelegateContext(policy, getParent());
                    this.start = System.currentTimeMillis();
                    // 判断是否允许重试
                    retryable = this.policy.canRetry(this.context);
                }
                // [0,openTimeout)
                else if (time < this.openWindow) {
                    // 指定开关打开
                    if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
                        logger.trace("Opening circuit");
                        setAttribute(CIRCUIT_OPEN, true);
                    }
                    this.start = System.currentTimeMillis();
                    return true;
                }
            }
            // 允许重试
            else {
                // (openWindow,resetTimeout]
                if (time > this.openWindow) {
                    logger.trace("Resetting context");
                    // 重建上下文
                    this.start = System.currentTimeMillis();
                    this.context = createDelegateContext(policy, getParent());
                }
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Open: " + !retryable);
            }
            // 设置开关打开的标志位,不能重试=开关打开
            setAttribute(CIRCUIT_OPEN, !retryable);
            return !retryable;
        }

        @Override
        public int getRetryCount() {
            return this.context.getRetryCount();
        }

        @Override
        public String toString() {
            return this.context.toString();
        }

    }

}

    除了断路器策略( CircuitBreakerRetryPolicy)稍微复杂点,其他基本都像 SimpleRetryPolicy一样,逻辑简单。

    首先 canRetry在模板方法内是判断是否要重试的实现。 SimpleRetryPolicy实现就是判断了次数和异常,是否满足要求。 CircuitBreakerRetryPolicy,主要逻辑集中在  org.springframework.retry.policy.CircuitBreakerRetryPolicy.CircuitBreakerRetryContext#isOpen,开关打开不允许重试,否则根据代理类判断是否允许重试。

   

    重试策略看完,再来看回退策略代码就更简单了,看一个固定时长的回退策略实现:

public class FixedBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<FixedBackOffPolicy> {
    // 默认使用 Thread.sleep等待
	private Sleeper sleeper = new ThreadWaitSleeper();

    // 可以指定 org.springframework.retry.backoff.ObjectWaitSleeper
	public void setSleeper(Sleeper sleeper) {
		this.sleeper = sleeper;
	}

	protected void doBackOff() throws BackOffInterruptedException {
		try {
			sleeper.sleep(backOffPeriod);
		}
		catch (InterruptedException e) {
			throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
		}
	}
}

    默认的就是 Thread.sleep一段时间。  

 

    至此,编程式的源码简单的流程已经了解了。接下来分析下注解的实现原理,可预见的,注解也借助了编程式这套模板。

    大体逻辑主要分为应用启动时,对全局内 @Retryable方法的收集,以及运行期的动态代理。

@Configuration
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware {


	@PostConstruct
	public void init() {
		Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
		retryableAnnotationTypes.add(Retryable.class);
        // 切点定义,也就是所有被 @Retryable标识的方法
		this.pointcut = buildPointcut(retryableAnnotationTypes);
        // 切面构建
		this.advice = buildAdvice();
		if (this.advice instanceof BeanFactoryAware) {
			((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
		}
	}

	protected Advice buildAdvice() {
        // 看这个实现
		AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
		if (retryContextCache != null) {
			interceptor.setRetryContextCache(retryContextCache);
		}
		if (retryListeners != null) {
			interceptor.setListeners(retryListeners);
		}
		if (methodArgumentsKeyGenerator != null) {
			interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
		}
		if (newMethodArgumentsIdentifier != null) {
			interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
		}
		if (sleeper != null) {
			interceptor.setSleeper(sleeper);
		}
		return interceptor;
	}
}

    上面配置类,init 生命中期内定义了切点和切面,切点很简单,就是所有 @Retryable标识的方法;切面的话需要移步  AnnotationAwareRetryOperationsInterceptor

public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取切面代理
		MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
		if (delegate != null) {
            // 执行代理方法
			return delegate.invoke(invocation);
		}
		else {
			return invocation.proceed();
		}
	}
}

    这里切面的构建并不是我们关心的,所以 getDelegate 感兴趣的可以自行研究,里面加了一道缓存,保证代理对象只会创建一次。

    继续跟源码,可以找到代理切面实现类根据有无状态有两种(根据 @Retryable#stateful()设置):

  •   org.springframework.retry.interceptor.RetryOperationsInterceptor
  • org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor  (@CircuitBreaker强制是这种
public class RetryOperationsInterceptor implements MethodInterceptor {

    private RetryOperations retryOperations = new RetryTemplate();

    public Object invoke(final MethodInvocation invocation) throws Throwable {

        String name;
        if (StringUtils.hasText(label)) {
            name = label;
        } else {
            name = invocation.getMethod().toGenericString();
        }
        final String label = name;
        // 构建 org.springframework.retry.RetryCallback
        RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(
                invocation, label) {

            @Override
            public Object doWithRetry(RetryContext context) throws Exception {

                context.setAttribute(RetryContext.NAME, label);

                if (invocation instanceof ProxyMethodInvocation) {
                    try {
                        // 回调业务代码块
                        return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
                    } catch (Exception e) {
                        throw e;
                    } catch (Error e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new IllegalStateException(e);
                    }
                } else {
                    throw new IllegalStateException(
                            "MethodInvocation of the wrong type detected - this should not happen with Spring AOP, "
                                    + "so please raise an issue if you see this exception");
                }
            }

        };

        // 根据是否有 @Recover 兜底/补偿方法,调用模板的不同方法
        if (recoverer != null) {
            RetryOperationsInterceptor.ItemRecovererCallback recoveryCallback = new RetryOperationsInterceptor.ItemRecovererCallback(invocation.getArguments(), recoverer);
            return this.retryOperations.execute(retryCallback, recoveryCallback);
        }

        return this.retryOperations.execute(retryCallback);

    }

}
public class StatefulRetryOperationsInterceptor implements MethodInterceptor {

    private RetryOperations retryOperations;

    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing proxied method in stateful retry: " + invocation.getStaticPart() + "("
                    + ObjectUtils.getIdentityHexString(invocation) + ")");
        }

        Object[] args = invocation.getArguments();
        Object defaultKey = Arrays.asList(args);
        if (args.length == 1) {
            defaultKey = args[0];
        }

        Object key = createKey(invocation, defaultKey);
        RetryState retryState = new DefaultRetryState(key,
                this.newMethodArgumentsIdentifier != null && this.newMethodArgumentsIdentifier.isNew(args),
                this.rollbackClassifier);

        Object result = this.retryOperations.execute(new StatefulRetryOperationsInterceptor.StatefulMethodInvocationRetryCallback(invocation, label),
                this.recoverer != null ? new StatefulRetryOperationsInterceptor.ItemRecovererCallback(args, this.recoverer) : null, retryState);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Exiting proxied method in stateful retry with result: (" + result + ")");
        }

        return result;
    }
}

    最终 invoke的实现里面,可以看到调用模板方法。利用切面,省得每个方法都写一遍 execute方法。    

 

总结

    总之,重试框架并不复杂,已经有现成的工具,就不要重复造轮子。本文也是通过阅读源码后写出来的,如果有理解不正确的地方,欢迎各位指正。

Java程序员博客系统推荐!我调研了100来个 Java 开源博客系统,发现这 5 个最好用!

$
0
0

最近想倒腾一下博客,看了很多现成的比较成熟的开源博客系统,自己也简单从下面几个维度总结对比了一下:

  1. star数量
  2. 技术选型
  3. 社区生态

当然啦!好东西不能独享。下面简单分享一下我所做的笔记。

欢迎小伙伴们评论区补充完善。ღ( ´・ᴗ・` )比心

halo

  • Github地址 : github.com/halo-dev/ha…
  • Star : 16.2k
  • 简介 :✍ 一个优秀的开源博客发布应用。
  • 技术 :Spring Boot+JPA+Hutool
  • 推荐等级 :⭐⭐⭐⭐⭐
  • 评价 :这款博客生态非常好(可选主题也非常多),使用的人也非常多。并且!! 还提供了小程序端!另外,搭建步骤也非常简单,基本是傻瓜式的。

Halo 首页:

Halo首页-halo.run

Halo 主题仓库 :

主题仓库- Halo-halo.run

Halo 博客效果:

 halo-寒山志-baozi.fun

OneBlog

  • Github地址: gitee.com/yadong.zhan…
  • Star : 2.3k
  • 简介 :一个简洁美观、功能强大并且自适应的Java博客。使用Spring Boot开发,前端使用Bootstrap。支持移动端自适应,配有完备的前台和后台管理功能。
  • 技术 : Springboot + Shiro + MySQL + Mybatis + Redis
  • 推荐等级 :⭐⭐⭐⭐
  • 评价 :我个人比较喜欢的一款博客样式类型( 不过,需要花更多时间自定义和完善。没精力折腾的,慎入!),自带评论系统、SEO等功能。比较适合做知识沉淀类网站。

Artificial-Intelligence-Algorithm-Scientist-www.piqiandong.com

solo

  • Github地址: github.com/88250/solo
  • Star : 0.8k
  • 简介 : Solo是一款小而美的开源博客系统,专为程序员设计。 Solo是B3log 分布式社区的 Java 博客端节点系统,欢迎加入下一代社区网络。
  • 技术 :Docker+H2+Nginx+ Latke(作者自研的以 JSON 为主的 Java Web 框架)
  • 推荐等级:⭐⭐⭐⭐
  • 评价 :和 halo 一样,都是比较成熟的博客系统了,并且生态特别好。Solo 第一个版本是在 2020 年发布,到现在为止,Solo项目的作者已经维护这个项目快10年了。为你们点赞!感谢你们的付出!另外,需要格外说明一下: 项目框架不是选用的主流的 Spring Boot 而是作者自己写的一个叫做 Latke的web 框架。

solo 博客效果:

D的个人博客

蘑菇博客

  • Github地址: gitee.com/moxi159753/…
  • Star: 1.2k
  • 简介:蘑菇博客(MoguBlog),一个基于微服务架构的前后端分离博客系统。
  • 技术 :Spring Boot + Spring Cloud Alibaba + MyBatis-Plus + ElasticSearch
  • 推荐等级:⭐⭐⭐⭐
  • 评价:第一次看到基于微服务架构的个人博客系统。我觉得作者可能是为了检验自己对于微服务相关框架的掌握,正如作者说的那样:“现在挺多是SSM或者SSH的博客管理系统,想用spring boot + spring cloud + vue 的微服务架构进行尝试项目的构建,里面很多功能可能只是为了满足自己的学习需求而引入的,因此本博客也是一个非常好的SpringBoot、SpringCloud以及Vue技术的入门学习项目。”

蘑菇博客前台效果:

 蘑菇博客-专注于技术分享的博客平台-demoweb.moguit.cn

蘑菇博客后台效果:

蘑菇云后台管理系统-demoadmin.moguit.cn

plumemo

  • Github地址 : github.com/byteblogs16…
  • Star: 0.3k
  • 简介:基于 SpringBoot实现零配置让系统的配置更简单,使用了 Mybatis-Plus快速开发框架,在不是复杂的查询操作下,无需写sql就可以快速完成接口编写。 后台管理系统使用了vue中流行的 ant,另外前后交互使用了 JWT作为令牌,进行权限、登录校验。。
  • 技术 :Spring boot + MyBatis-Plus + JWT
  • 推荐等级:⭐⭐⭐⭐
  • 评价 :界面简单美观,基于 Spring Boot 开发,适合用来学习,同时适合用来作为自己的博客。

plumemo博客后台效果:

plumemo-qfdxz.top

以上就是我今天要推荐的所有博客了。花了比较长时间去搜索以及对比,希望能对JavaGuide的小可爱们的有帮助!ღ( ´・ᴗ・` )比心

如果有帮助的话,不要吝啬你们手中的在看和赞!“怼”起来!

以上 4 本优质 原创 PDF微信搜“ JavaGuide”后台回复“ 面试突击”即可免费领取。

想要搭建个论坛?Guide哥调研了100来个 Java 开源论坛系统,发现这 5 个最好用!

$
0
0

大家好!我是 Guide 哥,Java 后端开发。一个会一点前端,喜欢烹饪的自由少年。

最近有点小忙。但是,由于前几天答应了一位读者自己会推荐一些开源的论坛系统,所以,昨晚就简单地熬了个夜,对比了很多个开源论坛系统之后,总结成了这篇文章。

这篇文章我一共推荐了 5 个论坛类开源项目,除了有 1 个是基于 PHP 开发之外,其他都是基于 Java ,并且大部分都是基于 Spring Boot 这个主流框架来做的。

欢迎小伙伴们在评论区补充啊!ღ( ´・ᴗ・` )比心

1. NiterForum

  • Github 地址: github.com/yourkevin/N…
  • 官网地址: niter.cn/forum
  • Star : 0.5k
  • 简介:尼特社区-NiterForum-一个论坛程序,几乎具有一个论坛/社区所应该有的全部功能-后端 Springboot/MyBatis/Maven/MySQL-前端 Thymeleaf/Layui-可供初学者,学习、交流使用。
  • 技术栈: 后端 Springboot + MyBatis + Maven + MySQL 前端 Thymeleaf + Layui
  • 推荐等级 :⭐⭐⭐⭐⭐
  • 评价:可以说 NiterForum 提供了一个论坛所能提供的所有功能,功能特性覆盖的非常全面。但这并不是这次推荐他的主要原因。作为本次论坛项目中第一个推荐的项目,NiterForum 的 NB 之处就是:他提供 NiterApp,完美适配了 NiterForum,支持 app 端扫码登录!

2. Symphony

  • Github 地址: github.com/88250/symph…
  • 官网地址: ld246.com/
  • Star : 0.7k
  • 简介: 一款用 Java 实现的现代化社区(论坛/问答/BBS/社交网络/博客)系统平台。
  • 技术栈: Latke (作者自研的以 JSON 为主的 Java Web 框架)+ jsoup + Jodd
  • 推荐等级 :⭐⭐⭐⭐
  • 评价:讲真,Symphony 是笔者目前见过的论坛项目中功能最齐全的一款(没有之一),满足多维需求:面向内容、面向知识问答、面向用户分享、交友、游戏等。而且 Symphony 风格时尚,充满创新、好玩的特性。交互体验一级棒。这个项目的缺点也很明显,那就是项目使用的技术栈不是主流,比较小众( 不过,作者自研 Java Web 框架的精神还是非常值得赞赏的!)。

sym

3. 码问社区

  • Github 地址: github.com/codedrinker…
  • 官网地址: www.mawen.co/?sort=hot
  • Star : 1.1k
  • 简介:开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。
  • 技术栈:SpringBoot + MyBatis+MySQL/H2+Flyway
  • 推荐等级 :⭐⭐⭐⭐⭐
  • 评价:码问社区的作者是阿里巴巴的一位大佬,开源了很多有意思的项目,码问社区就是其中一款,采用 SpringBoot + Vue 等主流技术栈打造,并配有整个开发过程的 视频讲解实战项目首推。

4. MDclub

  • Github 地址: github.com/zdhxiong/md…
  • 官网地址: community.mdclub.org/
  • Star : 0.5k
  • 简介:MDClub 漂亮、轻量且好用,它能让在线讨论变得更加轻松愉悦
  • 技术栈:PHP+MySQL
  • 推荐等级 :⭐⭐⭐⭐
  • 评价 :MDclub 是一款简约风格的论坛项目。漂亮、轻量且容易上手。代码实现基于 MDUI 框架,分层分明。网站适配多种终端,从手机、ipad 到大屏显示器,均能自动适配,并且提供根据操作系统的主题,自动切换亮色主题和暗色主题。这个特性真的超赞的~

mdclub.png

5. 朋也社区

  • Github 地址: github.com/tomoya92/py…
  • 官网地址: tomoya92.github.io/pybbs/
  • Star : 1.1 k
  • 简介:更实用的 Java 开发的社区(论坛)
  • 技术栈:Spring-Boot + Mybatis-Plus + MySQL
  • 推荐等级 :⭐⭐⭐⭐
  • 评价:朋也社区基于 Java 语言,采用主流的 Java Web 开发框架(SpringBoot)进行开发。个人觉得朋也社区最大的亮点是在设计层面上支持高度的可定制化。要实现这点很不容易,需要有很强的设计能力,并且朋也社区在实现过程对于各种集成的服务支持配置化(可随意开启或关闭)。

我是 Guide 哥,一 Java 后端开发,会一点前端,自由的少年。我们下期再见!微信搜“ JavaGuide”回复“ 面试突击”领取我整理的 4 本原创PDF

推荐一款MySQL开源客户端,免费+跨平台+使用便捷!

$
0
0

最近无意间发现了一款 开源免费的 MySQL 客户端管理工具,磊哥试用了两天感觉还行,所以今天推荐给各位大佬。

此工具不止是开源免费的,而且可以跨平台使用,如 Windows、MacOS 都可以轻松支持,并且运行速度也是杠杠的。

话不多说,先来看操作界面,如下图所示: mysql-vs.png

再来看工具使用的动图: mysql-client-gif.gif 从上图可以看出此工具 有超级实用的关键字提示功能,还可以很直观的显示数据库和表信息,我们只需要按快捷键 F9 就可以执行选中的 SQL 语句了。

工具安装

有些朋友可能已经看出来了,这个熟悉的页面好像 VSCode。其实它就是基于 VSCode 的 MySQL 管理工具,所以也就不难理解为什么它是免费且跨平台的工具了吧,哈哈。

有了 VSCode 的加持,连工具安装都省了,只需要在安装好的 VSCode 上装一个插件就可以直接操作 MySQL 了,如下图所示: mysqlclient-01.png 在扩展工具里搜索:MySQL,点击 MySQL Client for vscode 选项进行安装,安装完成之后 VSCode 的扩展工具底下就会出现一个数据库的图标: image.png 点击图标就可以进行 MySQL 的操作了。

开源地址

github.com/formulahend…

功能介绍

1.导出 Excel

image.png 我们可以将查询的结果很方便的导出到 Excel 文件,如上图所示,只需要点击分享选择“Export”即可。

2.显示/隐藏列

image.png 我们可以通过勾选来选择我们需要查看的数据(显示/隐藏列),而不是通过 Select语句进行控制。

3.手动插入数据

image.png 我们可以通过点击插入按钮来给表中插入数据。

稳定性及支持版本

此工具支持最新版的 MySQL 8,于是磊哥有特意查询了这个工具的发布日志,发现它的更新频率还是挺高的,如下图所示: mysql-client-02.png 并且我从最早的日志可以看出,它在 2017 年就发布了第一版,至今已经有 3 年的历史了,因此各位大佬可以安心使用。

PS:如果真的有解决不了的 BUG,我们还可以自己动手 Fork 一个分支进行二次开发,或者直接 Issues 一波。

总结

本文推荐的 MySQL 客户端是基于 VSCode 的,因此它可以支持任意平台。如果已经安装了 VSCode 的朋友只需要安装此插件就可以正常使用了。此插件是开源免费的,首次发布于 2017 年,更新频率比较高比较靠谱,支持 MySQL 最新的版本,功能也都够用,所以推荐给各位朋友,如果某天不想满世界找破解版软件了,或者想更快速的操作 MySQL,那么可以试试这款插件。

文末福利:搜索公众号「Java中文社群」发送“面试”,领取最新的面试资料。

推荐一个被阿里巴巴官方高度认可的vue生态中交互、体验、逻辑处理超棒的国产admin框架ant-design-vue-pro,免费开源,文档全面,希望国内开发者支持认真做事的手...

$
0
0
推荐一个被阿里巴巴官方高度认可的vue生态中交互、体验、逻辑处理超棒的国产admin框架ant-design-vue-pro,免费开源,文档全面,希望国内开发者支持认真做事的手艺人,不要总想着投机取巧,踏踏实实做事。
演示地址:
https://preview.pro.antdv.com/
开源地址:
https://gitee.com/sendya/ant-design-pro-vue



一文了解我国贡献给CNCF云原生基金会开源项目(201025)

$
0
0

今天整理下我国贡献给CNCF云原生基金会的开源项目,对于云原生解决方案我在前面写过相应的文章进行介绍,但是没有系统的整理过我国贡献到CNCF的开源项目。今天结合CNCF最新的云原生全景图,做下初步整理,由于项目太多也仅仅只能整理一些重要项目。

CNCF基金会和开源项目孵化

一文了解我国贡献给CNCF云原生基金会开源项目

 

CNCF,英文全称为Cloud Native Computing Foundation,中文译为“云原生计算基金会”。成立于2015年12月11日。CNCF是Linux基金会旗下的基金会,可以理解为一个非盈利组织。

当年谷歌内部一直用于编排容器的Borg项目开源了,为了该项目更好的发展,谷歌与Linux基金会一起创办了CNCF。同时,谷歌把Borg用Go语言重写,更名为Kubernetes并捐赠到CNCF。

成立这个组织的初衷或者愿景,简单说就是推动云原生计算可持续发展;帮助云原生技术开发人员快速地构建出色的产品。

根据CNCF基金会官网我们可以,最近几年CNCF组织和开源项目加入发展迅猛,看到已经有超过200个以上的项目加入了CNCF开源项目中。而对于国内的类似腾讯,华为,阿里,青云,京东等也都是基金会的重要成员单位。

如上图所示,CNCF项目分为Sandbox、Incubating和Graduation三个阶段。根据成熟度级别,每个CNCF项目会逐步走过它的早期Sandbox以及孵化阶段从而毕业。

被CNCF接受并成为Sandbox项目需要至少2个TOC的sponsor的支持才能够进入到正式的孵化阶段。而到了孵化阶段的项目需要满足所有Sandbox的需求,并且必须提供至少三个独立的终端用户成功地使用在生产环境中的资料信息,在质量和范围方面都能够得到证明才能够正式毕业。

虽然当前加入的项目很多,可以看到,实际当前已经毕业的项目很少,只有类似Kubernetes, Prometheus,Envoy,CoreDNS等几个项目已经完全毕业。而其它诸多项目都还处于沙箱和孵化阶段中。

自动化,配置中心和容器管理

一文了解我国贡献给CNCF云原生基金会开源项目

 

KubeEdge

在19年3 月,CNCF 基金会及技术委员会全体一致同意开源智能边缘项目 KubeEdge 加入 CNCF 社区,成为 CNCF 在智能边缘领域的首个正式项目。

KubeEdge 全面支持端、边、云协同、服务网格通信能力,提升了性能和节点管理规模。依托 KubeEdge 原生开源边缘计算平台,边缘计算将云端计算能力延伸到靠近终端设备的边缘节点,目前已经广泛应用于工程质检、OCR、人脸识别、驾驶行为分析等边缘场景。

Apollo

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。

对于Apollo当前已经成为一个大家主流采用的应用到微服务架构开发,特别是多团队协同中的配置中心。特别是对于配置信息实时刷新,缓存,配置信息维度管理,前台提供操作界面等方面都优于Spring Config配置中心。

HarBor

由 VMware 公司中国团队为企业用户设计的 Registry server 开源项目,包括了权限管理(RBAC)、LDAP、审计、管理界面、自我注册、HA 等企业必需的功能,作为一个企业级私有 Registry 服务器,Harbor 提供了更好的性能和安全。提升用户使用 Registry 构建和运行环境传输镜像的效率。

当前在DevOps和容器云的集成方案中,HarBor成为一个主流采用的镜像库管理和存储方式。当前HarBor已经处于毕业状态。

Dragonfly

Dragonfly(蜻蜓)是阿里自研的 P2P 文件分发系统,用于解决大规模文件分发场景下分发耗时、成功率低、带宽浪费等难题。大幅提升发布部署、数据预热、大规模容器镜像分发等业务能力。

开源版的 Dragonfly 可用于 P2P 文件分发、容器镜像分发、局部限速、磁盘容量预检等。它支持多种容器技术,对容器本身无需做任何改造,镜像分发比 natvie 方式提速可高达 57 倍,Registry 网络出流量降低99.5%以上。

容器编排,协同,服务注册,远程调用

一文了解我国贡献给CNCF云原生基金会开源项目

 

对于容器编排,当前由Google贡献的Kubernetes已经成为主流,不再叙述。

KubeSphere(QKE)

KubeSphere 是一款独立的分布式容器管理平台,这是青云 QingCloud 正式发布的开源项目,已于今年4 月份正式加入CNCF 和Linux 基金会,本次大会发布了高级版交付QKE(QingCloud Kubernetes Engine) 服务,这在提供K8s 集群内核的基础上,屏蔽掉底层IaaS 运维管理复杂度之外,同时还完整继承了KubeSphere 体系化的平台层管理功能、DevOps 和微服务治理工具,监控告警工具以及应用管理组件,帮助开发者以最低成本完成容器转型。

Nacos

阿里开源的Nacos服务注册中心和服务配置中心。当前也是被大量在微服务架构应用中采用。

Nacos 支持基于 DNS 和基于 RPC 的服务发现(可以作为springcloud的注册中心)、动态配置服务(可以做配置中心)、动态 DNS 服务。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构(例如微服务范式、云原生范式)的服务基础设施。

Dubbo

阿里开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

SOFA-RPC

蚂蚁金服自主研发的分布式的系统中间件,包含金融级云原生的架构所需要的各种组件,其中包括微服务的研发框架、RPC调用框架、服务的注册中心、分布式定时任务、限流/熔断机制、动态配置的推送功能、分布式的链路追踪、Metrics监控、分布式高可用队列、分布式事务框架、分布式数据库代理等组件。是在金融领域完整的分布式解决方案。

可以看到该解决方案提供基于RPC调用的完整接口服务治理管控能力。

蚂蚁金服金融级分布式架构 SOFAStack 中的 3 个项目加入这次最新版本的 Cloud Native Landscape ,分别是 SOFAMosn、分布式链路跟踪系统 SOFATracer、RPC 服务框架 SOFARPC 。

Tars

腾讯开源的Tars,不仅仅是一个高性能的RPC调用框架,也可以看作是一个完整的基于RPC的微服务开发和治理框架。Tars 是基于名字服务使用 Tars 协议的高性能 RPC 开发框架,同时配套一体化的服务治理平台,帮助个人或者企业快速的以微服务的方式构建自己稳定可靠的分布式应用。

API网关和服务网格

一文了解我国贡献给CNCF云原生基金会开源项目

 

对于API网关,可以看到Kong网关,暂时没有看到国内开源贡献到CNCF的API网关产品。对于GoKu实际上是一款不错的API网关产品,基于go语言,性能也不错,但是处于半开源状态。

Sentinel

阿里开源的Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进。

对于ServiceMesh部分,在前面谈云原生的时候曾经谈到华为和阿里都有自己的Mesher解决方案,但是在当前的CNCF全景图里面暂时没有看到。

监控,日志和链路跟踪

一文了解我国贡献给CNCF云原生基金会开源项目

 

在这块当前可以看到,应用到容器云环境的主流还是Google开源的Prometheus监控。当前很多云原生解决方案中也进行了集成。

SkyWalking

Skywalking是由国内开源爱好者吴晟(原OneAPM工程师,目前在华为)开源并提交到Apache孵化器的产品,它同时吸收了Zipkin/Pinpoint/CAT的设计思路,支持非侵入式埋点。是一款基于分布式跟踪的应用程序性能监控系统。另外社区还发展出了一个叫OpenTracing的组织,旨在推进调用链监控的一些规范和标准工作。

当前对于SkyWalking也是常用的微服务架构体系中的服务链路跟踪工具。

SOFATracer

蚂蚁金服开源的服务链路跟踪工具,前面已经做过介绍。

Alibaba Cloud Log Service

阿里开源的阿里云日志服务

听云

当前商用的APM主流的包括OneAPM和听云,在云原生全景图里面有听云,但是实际APM产品并没有开源,或者不清楚具体开源了哪个部分的内容。

数据库和流处理

一文了解我国贡献给CNCF云原生基金会开源项目

 

TiKV和TiDB

TiKV 是一个分布式事务键值数据库,最初是为了补充 TiDB 而创建的。TiKV 采用 Rust 构建,由 Raft 提供支持,受到 Google Spanner 和 HBase 设计的启发,提供简化的调度和自动平衡,但不依赖于任何分布式文件系统。

TiDB 是 PingCAP 公司自主设计、研发的开源分布式关系型数据库,是一款同时支持在线事务处理与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP)的融合型分布式数据库产品,具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 协议和 MySQL 生态等重要特性。

刘奇,PingCAP创始人&CEO。开源黑客,知名开源项目 TiDB / TiKV / Codis 作者,曾任职豌豆荚 / 京东,擅长分布式数据库和分布式缓存。

应用开发和持续集成

一文了解我国贡献给CNCF云原生基金会开源项目

 

ServiceComb

ServiceComb 作为 Apache 开源组织下的一款微服务框架,其前身为华为云的 微服务引擎 CSE (Cloud Service Engine) 云服务。它意味着国内一款微服务框架在华为和 Apache 组织的共同努力下,随着微服务市场的火爆,一定会让越来越多的开发者所喜欢。

存储

一文了解我国贡献给CNCF云原生基金会开源项目

 

YRCloudFile

近日,CNCF 发布最新版本的 Cloud Native Landscape,焱融云的存储软件 YRCloudFile 被列入云原生全景图谱,位于云原生存储象限中,是中国首个被收录的容器持久化存储产品。YRCloudFile 是焱融云推出的一款全面支持容CNCF器存储的高性能分布式文件存储产品,可支持多种容器编排框架,为企业用户提供容器持久化存储服务。



 

MySQL如何实时同步数据到ES?试试这款阿里开源的神器!

$
0
0

SpringBoot实战电商项目mall(40k+star)地址: github.com/macrozheng/…

摘要

mall项目中的商品搜索功能,一直都没有做实时数据同步。最近发现阿里巴巴开源的 canal可以把MySQL中的数据实时同步到Elasticsearch中,能很好地解决数据同步问题。今天我们来讲讲 canal的使用,希望对大家有所帮助!

canal简介

canal主要用途是对MySQL数据库增量日志进行解析,提供增量数据的订阅和消费,简单说就是可以对MySQL的增量数据进行实时同步,支持同步到MySQL、Elasticsearch、HBase等数据存储中去。

canal工作原理

canal会模拟MySQL主库和从库的交互协议,从而伪装成MySQL的从库,然后向MySQL主库发送dump协议,MySQL主库收到dump请求会向canal推送binlog,canal通过解析binlog将数据同步到其他存储中去。

canal工作原理图

canal使用

接下来我们来学习下canal的使用,以MySQL实时同步数据到Elasticsearch为例。

组件下载

  • 首先我们需要下载canal的各个组件 canal-servercanal-adaptercanal-admin,下载地址:https://github.com/alibaba/canal/releases

  • canal的各个组件的用途各不相同,下面分别介绍下:

    • canal-server(canal-deploy):可以直接监听MySQL的binlog,把自己伪装成MySQL的从库,只负责接收数据,并不做处理。
    • canal-adapter:相当于canal的客户端,会从canal-server中获取数据,然后对数据进行同步,可以同步到MySQL、Elasticsearch和HBase等存储中去。
    • canal-admin:为canal提供整体配置管理、节点运维等面向运维的功能,提供相对友好的WebUI操作界面,方便更多用户快速和安全的操作。
  • 由于不同版本的MySQL、Elasticsearch和canal会有兼容性问题,所以我们先对其使用版本做个约定。

应用端口版本
MySQL33065.7
Elasticsearch92007.6.2
Kibanba56017.6.2
canal-server111111.1.15
canal-adapter80811.1.15
canal-admin80891.1.15

MySQL配置

  • 由于canal是通过订阅MySQL的binlog来实现数据同步的,所以我们需要开启MySQL的binlog写入功能,并设置 binlog-format为ROW模式,我的配置文件为 /mydata/mysql/conf/my.cnf,改为如下内容即可;
[mysqld]
## 设置server_id,同一局域网中需要唯一
server_id=101 
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql  
## 开启二进制日志功能
log-bin=mall-mysql-bin  
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M  
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=row  
## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7  
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062  
复制代码
  • 配置完成后需要重新启动MySQL,重启成功后通过如下命令查看binlog是否启用;
show variables like '%log_bin%'
复制代码
+---------------------------------+-------------------------------------+
| Variable_name                   | Value                               |
+---------------------------------+-------------------------------------+
| log_bin                         | ON                                  |
| log_bin_basename                | /var/lib/mysql/mall-mysql-bin       |
| log_bin_index                   | /var/lib/mysql/mall-mysql-bin.index |
| log_bin_trust_function_creators | OFF                                 |
| log_bin_use_v1_row_events       | OFF                                 |
| sql_log_bin                     | ON                                  |
+---------------------------------+-------------------------------------+
复制代码
  • 再查看下MySQL的binlog模式;
show variables like 'binlog_format%';  
复制代码
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
复制代码
  • 接下来需要创建一个拥有从库权限的账号,用于订阅binlog,这里创建的账号为 canal:canal
CREATE USER canal IDENTIFIED BY 'canal';  
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;
复制代码
  • 创建好测试用的数据库 canal-test,之后创建一张商品表 product,建表语句如下。
CREATE TABLE `product`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `sub_title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `price` decimal(10, 2) NULL DEFAULT NULL,
  `pic` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
复制代码

canal-server使用

  • 将我们下载好的压缩包 canal.deployer-1.1.5-SNAPSHOT.tar.gz上传到Linux服务器,然后解压到指定目录 /mydata/canal-server,可使用如下命令解压;
tar -zxvf canal.deployer-1.1.5-SNAPSHOT.tar.gz
复制代码
  • 解压完成后目录结构如下;
├── bin
│   ├── restart.sh
│   ├── startup.bat
│   ├── startup.sh
│   └── stop.sh
├── conf
│   ├── canal_local.properties
│   ├── canal.properties
│   └── example
│       └── instance.properties
├── lib
├── logs
│   ├── canal
│   │   └── canal.log
│   └── example
│       ├── example.log
│       └── example.log
└── plugin
复制代码
  • 修改配置文件 conf/example/instance.properties,按如下配置即可,主要是修改数据库相关配置;
# 需要同步数据的MySQL地址
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=
# 用于同步数据的数据库账号
canal.instance.dbUsername=canal
# 用于同步数据的数据库密码
canal.instance.dbPassword=canal
# 数据库连接编码
canal.instance.connectionCharset = UTF-8
# 需要订阅binlog的表过滤正则表达式
canal.instance.filter.regex=.*\\..*
复制代码
  • 使用 startup.sh脚本启动 canal-server服务;
sh bin/startup.sh
复制代码
  • 启动成功后可使用如下命令查看服务日志信息;
tail -f logs/canal/canal.log
复制代码
2020-10-26 16:18:13.354 [main] INFO  com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[172.17.0.1(172.17.0.1):11111]
2020-10-26 16:18:19.978 [main] INFO  com.alibaba.otter.canal.deployer.CanalStarter - ## the canal server is running now ......
复制代码
  • 启动成功后可使用如下命令查看instance日志信息;
tail -f logs/example/example.log 
复制代码
2020-10-26 16:18:16.056 [main] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
2020-10-26 16:18:16.061 [main] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
2020-10-26 16:18:18.259 [main] INFO  c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example 
2020-10-26 16:18:18.282 [main] WARN  c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^.*\..*$
2020-10-26 16:18:18.282 [main] WARN  c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter : ^mysql\.slave_.*$
2020-10-26 16:18:19.543 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN  c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> begin to find start position, it will be long time for reset or first position
2020-10-26 16:18:19.578 [main] INFO  c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....
2020-10-26 16:18:19.912 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN  c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - prepare to find start position just last position
 {"identity":{"slaveId":-1,"sourceAddress":{"address":"localhost","port":3306}},"postion":{"gtid":"","included":false,"journalName":"mall-mysql-bin.000006","position":2271,"serverId":101,"timestamp":1603682664000}}
2020-10-26 16:18:22.435 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN  c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> find start position successfully, EntryPosition[included=false,journalName=mall-mysql-bin.000006,position=2271,serverId=101,gtid=,timestamp=1603682664000] cost : 2768ms , the next step is binlog dump
复制代码
  • 如果想要停止 canal-server服务可以使用如下命令。
sh bin/stop.sh
复制代码

canal-adapter使用

  • 将我们下载好的压缩包 canal.adapter-1.1.5-SNAPSHOT.tar.gz上传到Linux服务器,然后解压到指定目录 /mydata/canal-adpter,解压完成后目录结构如下;
├── bin
│   ├── adapter.pid
│   ├── restart.sh
│   ├── startup.bat
│   ├── startup.sh
│   └── stop.sh
├── conf
│   ├── application.yml
│   ├── es6
│   ├── es7
│   │   ├── biz_order.yml
│   │   ├── customer.yml
│   │   └── product.yml
│   ├── hbase
│   ├── kudu
│   ├── logback.xml
│   ├── META-INF
│   │   └── spring.factories
│   └── rdb
├── lib
├── logs
│   └── adapter
│       └── adapter.log
└── plugin
复制代码
  • 修改配置文件 conf/application.yml,按如下配置即可,主要是修改canal-server配置、数据源配置和客户端适配器配置;
canal.conf:
  mode: tcp # 客户端的模式,可选tcp kafka rocketMQ
  flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效
  zookeeperHosts:    # 对应集群模式下的zk地址
  syncBatchSize: 1000 # 每次同步的批数量
  retries: 0 # 重试次数, -1为无限重试
  timeout: # 同步超时时间, 单位毫秒
  accessKey:
  secretKey:
  consumerProperties:
    # canal tcp consumer
    canal.tcp.server.host: 127.0.0.1:11111 #设置canal-server的地址
    canal.tcp.zookeeper.hosts:
    canal.tcp.batch.size: 500
    canal.tcp.username:
    canal.tcp.password:

  srcDataSources: # 源数据库配置
    defaultDS:
      url: jdbc:mysql://127.0.0.1:3306/canal_test?useUnicode=true
      username: canal
      password: canal
  canalAdapters: # 适配器列表
  - instance: example # canal实例名或者MQ topic名
    groups: # 分组列表
    - groupId: g1 # 分组id, 如果是MQ模式将用到该值
      outerAdapters:
      - name: logger # 日志打印适配器
      - name: es7 # ES同步适配器
        hosts: 127.0.0.1:9200 # ES连接地址
        properties:
          mode: rest # 模式可选transport(9300) 或者 rest(9200)
          # security.auth: test:123456 #  only used for rest mode
          cluster.name: elasticsearch # ES集群名称
复制代码
  • 添加配置文件 canal-adapter/conf/es7/product.yml,用于配置MySQL中的表与Elasticsearch中索引的映射关系;
dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值
destination: example  # canal的instance或者MQ的topic
groupId: g1 # 对应MQ模式下的groupId, 只会同步对应groupId的数据
esMapping:
  _index: canal_product # es 的索引名称
  _id: _id  # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配
  sql: "SELECT
        p.id AS _id,
        p.title,
        p.sub_title,
        p.price,
        p.pic
        FROM
        product p"        # sql映射
  etlCondition: "where a.c_time>={}"   #etl的条件参数
  commitBatch: 3000   # 提交批大小
复制代码
  • 使用 startup.sh脚本启动 canal-adapter服务;
sh bin/startup.sh
复制代码
  • 启动成功后可使用如下命令查看服务日志信息;
tail -f logs/adapter/adapter.log
复制代码
20-10-26 16:52:55.148 [main] INFO  c.a.o.canal.adapter.launcher.loader.CanalAdapterLoader - Load canal adapter: logger succeed
2020-10-26 16:52:57.005 [main] INFO  c.a.o.c.client.adapter.es.core.config.ESSyncConfigLoader - ## Start loading es mapping config ... 
2020-10-26 16:52:57.376 [main] INFO  c.a.o.c.client.adapter.es.core.config.ESSyncConfigLoader - ## ES mapping config loaded
2020-10-26 16:52:58.615 [main] INFO  c.a.o.canal.adapter.launcher.loader.CanalAdapterLoader - Load canal adapter: es7 succeed
2020-10-26 16:52:58.651 [main] INFO  c.alibaba.otter.canal.connector.core.spi.ExtensionLoader - extension classpath dir: /mydata/canal-adapter/plugin
2020-10-26 16:52:59.043 [main] INFO  c.a.o.canal.adapter.launcher.loader.CanalAdapterLoader - Start adapter for canal-client mq topic: example-g1 succeed
2020-10-26 16:52:59.044 [main] INFO  c.a.o.canal.adapter.launcher.loader.CanalAdapterService - ## the canal client adapters are running now ......
2020-10-26 16:52:59.057 [Thread-4] INFO  c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - =============> Start to connect destination: example <=============
2020-10-26 16:52:59.100 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8081"]
2020-10-26 16:52:59.153 [main] INFO  org.apache.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
2020-10-26 16:52:59.590 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8081 (http) with context path ''
2020-10-26 16:52:59.626 [main] INFO  c.a.otter.canal.adapter.launcher.CanalAdapterApplication - Started CanalAdapterApplication in 31.278 seconds (JVM running for 33.99)
2020-10-26 16:52:59.930 [Thread-4] INFO  c.a.otter.canal.adapter.launcher.loader.AdapterProcessor - =============> Subscribe destination: example succeed <=============
复制代码
  • 如果需要停止 canal-adapter服务可以使用如下命令。
sh bin/stop.sh
复制代码

数据同步演示

经过上面的一系列步骤,canal的数据同步功能已经基本可以使用了,下面我们来演示下数据同步功能。

  • 首先我们需要在Elasticsearch中创建索引,和MySQL中的product表相对应,直接在Kibana的 Dev Tools中使用如下命令创建即可;
PUT canal_product
{"mappings": {"properties": {"title": {"type": "text"
      },"sub_title": {"type": "text"
      },"pic": {"type": "text"
      },"price": {"type": "double"
      }
    }
  }
}
复制代码

  • 创建完成后可以查看下索引的结构;
GET canal_product/_mapping
复制代码

  • 之后使用如下SQL语句在数据库中创建一条记录;
INSERT INTO product ( id, title, sub_title, price, pic ) VALUES ( 5, '小米8', ' 全面屏游戏智能手机 6GB+64GB', 1999.00, NULL );
复制代码
  • 创建成功后,在Elasticsearch中搜索下,发现数据已经同步了;
GET canal_product/_search
复制代码

  • 再使用如下SQL对数据进行修改;
UPDATE product SET title='小米10' WHERE id=5
复制代码
  • 修改成功后,在Elasticsearch中搜索下,发现数据已经修改了;

  • 再使用如下SQL对数据进行删除操作;
DELETE FROM product WHERE id=5
复制代码
  • 删除成功后,在Elasticsearch中搜索下,发现数据已经删除了,至此MySQL同步到Elasticsearch的功能完成了!

canal-admin使用

  • 将我们下载好的压缩包 canal.admin-1.1.5-SNAPSHOT.tar.gz上传到Linux服务器,然后解压到指定目录 /mydata/canal-admin,解压完成后目录结构如下;
├── bin
│   ├── restart.sh
│   ├── startup.bat
│   ├── startup.sh
│   └── stop.sh
├── conf
│   ├── application.yml
│   ├── canal_manager.sql
│   ├── canal-template.properties
│   ├── instance-template.properties
│   ├── logback.xml
│   └── public
│       ├── avatar.gif
│       ├── index.html
│       ├── logo.png
│       └── static
├── lib
└── logs
复制代码
  • 创建canal-admin需要使用的数据库 canal_manager,创建SQL脚本为 /mydata/canal-admin/conf/canal_manager.sql,会创建如下表;

  • 修改配置文件 conf/application.yml,按如下配置即可,主要是修改数据源配置和 canal-admin的管理账号配置,注意需要用一个有读写权限的数据库账号,比如管理账号 root:root
server:
  port: 8089
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

spring.datasource:
  address: 127.0.0.1:3306
  database: canal_manager
  username: root
  password: root
  driver-class-name: com.mysql.jdbc.Driver
  url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
  hikari:
    maximum-pool-size: 30
    minimum-idle: 1

canal:
  adminUser: admin
  adminPasswd: admin
复制代码
  • 接下来对之前搭建的 canal-serverconf/canal_local.properties文件进行配置,主要是修改 canal-admin的配置,修改完成后使用 sh bin/startup.sh local重启 canal-server
# register ip
canal.register.ip =

# canal admin config
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# admin auto register
canal.admin.register.auto = true
canal.admin.register.cluster = 
复制代码
  • 使用 startup.sh脚本启动 canal-admin服务;
sh bin/startup.sh
复制代码
  • 启动成功后可使用如下命令查看服务日志信息;
tail -f logs/admin.log
复制代码
020-10-27 10:15:04.210 [main] INFO  org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8089"]
2020-10-27 10:15:04.308 [main] INFO  org.apache.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
2020-10-27 10:15:04.534 [main] INFO  o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8089 (http) with context path ''
2020-10-27 10:15:04.573 [main] INFO  com.alibaba.otter.canal.admin.CanalAdminApplication - Started CanalAdminApplication in 31.203 seconds (JVM running for 34.865)
复制代码
  • 访问canal-admin的Web界面,输入账号密码 admin:123456即可登录,访问地址:http://192.168.3.101:8089

  • 登录成功后即可使用Web界面操作canal-server。

参考资料

canal官方文档:https://github.com/alibaba/canal/wiki

配置文件地址

github.com/macrozheng/…

本文 GitHub github.com/macrozheng/…已经收录,欢迎大家Star!


开源API网关Kong基本介绍和安装验证(201129)

$
0
0

开源API网关Kong基本介绍和安装验证

 

今天准备介绍下开源API网关Kong,在Gtihub搜索API网关类的开源产品,可以看到Kong网关常年都是排第一的位置,而且当前很多都有一定研发能力的企业在API网关产品选型的时候基本也会选择Kong网关,并基于Kong网关进行二次开发和定制。

API网关概述

开源API网关Kong基本介绍和安装验证

 

简单来说API网关就是将所有的微服务提供的API接口服务能力全部汇聚进来,统一接入进行管理,也正是通过统一拦截,就可以通过网关实现对API接口的安全,日志,限流熔断等共性需求。如果再简单说下,通过网关实现了几个关键能力。

  • 内部的微服务对外部访问来说位置透明,外部应用只需和网关交互
  • 统一拦截接口服务,实现安全,日志,限流熔断等需求

从这里,我们就可以看到API网关和传统架构里面的ESB总线是类似的,这些关键能力本身也是ESB服务总线的能力,但是ESB服务总线由于要考虑遗留系统的接入,因此增加了:

  • 大量适配器实现对遗留系统的遗留接口适配,多协议转换能力
  • 进行数据的复制映射,路由等能力

对于两者,我原来做过一个简单的对比,大家可以参考。

开源API网关Kong基本介绍和安装验证

 

对于API网关进一步的功能介绍,可以我前面参考文章:

一文详细讲解API网关核心功能和API管理扩展

基于Openresty开发API网关

开源API网关Kong基本介绍和安装验证

 

在谈API网关前,我们先谈下Openresty。在前面文章谈到过,当前适合用于API网关的架构,一种是基于Openresty的,一种是基于Go语言的。

OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用系统 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

为何Openresty适合用于API网关开发?

对于API网关,有一个最基础的核心功能即需要实现接口服务代理路由,而这个本身也是Nginx反向代理能够提供的一个标准功能。

如果应用场景比较简单,仅仅是实现统一接口对外暴露,可以看到很多企业实际并没有采用API网关,而是直接采用了Nginx来替代API网关的服务代理路由功能。

其次,对于常规的API网关的中间件研发,在研发完成后本身还要依托一个Web容器进行部署,同时还需要自己去实现类似路由代理等各种能力。

在这种场景下可以看到,依托于Nginx,在其内部来扩展一个Web容器能力,既可以充分的利用Nginx本身的代理路由和性能优势就是一个重要的选择。而OpenResty本身可以看做是基于Nginx服务器,在其worker里面内嵌了一个LuaJVM,通过这种方式来实现了两者的融合。同时又可以通过开发和定制各种Lua库来进行快速的功能扩展。

也正是这样原因,基于Openresty来扩展动态网关功能是一个很好的选择。

OpenResty的安装

在Centos下可以通过yum很方便的安装OpenResty,具体如下:

//安装yum-utils
# sudo yum install yum-utils
//添加 openresty 仓库
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
//安装openresty
# sudo yum install openresty
//安装命令行工具
# sudo yum install openresty-resty
//至此安装成功,默认安装在  /usr/local/openresty
//测试
# sudo /sbin/service openresty start
# sudo /sbin/service openresty stop

注意在安装完成后如果通过浏览器访问,需要先关闭防火墙或者打开放行80端口。

Kong网关概述

开源API网关Kong基本介绍和安装验证

 

首先我们看下GitHub上对Kong网关的一些介绍

当我们决定对应用进行微服务改造时,应用客户端如何与微服务交互的问题也随之而来,毕竟服务数量的增加会直接导致部署授权、负载均衡、通信管理、分析和改变的难度增加。

面对以上问题,API GATEWAY是一个不错的解决方案,其所提供的访问限制、安全、流量控制、分析监控、日志、请求转发、合成和协议转换功能,可以解放开发者去把精力集中在具体逻辑的代码,而不是把时间花费在考虑如何解决应用和其他微服务链接的问题上。

Kong网关是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。

KONG本身提供包括HTTP基本认证、密钥认证、CORS、TCP、UDP、文件日志、API请求限流、请求转发及NGINX监控等基本功能。目前,Kong在Mashape管理了超过15,000个API,为200,000开发者提供了每月数十亿的请求支持。

Kong网关核心组件

  • Kong Server :基于nginx的服务器,用来接收API请求。
  • Apache Cassandra/PostgreSQL :用来存储操作数据。
  • Kong dashboard:官方推荐UI管理工具,也可以使用开源的Konga平台

Kong采用插件机制进行功能定制,当前本身已经具备了类似安全,限流,日志,认证,数据映射等基础插件能力,同时也可以很方便的通过Lua定制自己的插件。插件完全是一种可以动态插拔的模式,通过插件可以方便的实现Kong网关能力的扩展。

Kong网关核心组件关键特性

  • Cloud-Native云原生:与平台无关,Kong可以在从裸机到容器的任何平台上运行,并且可以在每个云上本地运行。
  • Kubernetes集成能力:使用官方的Ingress Controller通过本地Kubernetes CRD声明性地配置Kong,以路由和连接所有L4 + L7层网络流量。
  • 动态负载均衡:在多个上游服务之间平衡流量。
  • 基于哈希的负载均衡:具有一致的哈希/粘性会话的负载均衡。
  • 断路器:智能跟踪不健康的上游服务。
  • 健康检查:主动和被动监视上游服务。
  • 服务发现:在Consul之类的第三方DNS解析器中解析SRV记录。
  • Serverless:直接从Kong调用和保护AWS Lambda或OpenWhisk功能。
  • WebSockets:通过WebSockets与您的上游服务进行通信。
  • gRPC:与gRPC服务进行通信,并通过日志记录和可观察性插件观察流量
  • OAuth2.0:轻松将OAuth2.0身份验证添加到您的API。
  • 日志功能:通过HTTP,TCP,UDP或磁盘Log对系统的请求和响应。
  • 安全性:ACL,僵尸程序检测,允许/拒绝IP等...
  • Syslog:登录到系统日志。
  • SSL:为基础服务或API设置特定的SSL证书。
  • 监控:对关键负载和性能服务器指标提供动态实时监控。
  • 转发代理:使Kong连接到透明的中介HTTP代理。
  • 认证:HMAC,JWT,Basic等。
  • 限流:基于许多变量的阻止和限制请求。
  • 转换:添加,删除或处理HTTP请求和响应。
  • 缓存:在代理层缓存并提供响应。
  • CLI:从命令行控制Kong群集。
  • 开放API接口:Kong可以使用其RESTful API进行操作,以实现最大的灵活性。
  • 跨区域复制:跨不同区域的配置始终是最新的。
  • 故障检测和恢复:如果您的Cassandra节点之一发生故障,则Kong不会受到影响。
  • 群集:所有Kong节点自动加入群集,并在各个节点之间更新其配置。
  • 可扩展性:Kong本质上分布,只需添加节点即可水平扩展。
  • 性能:Kong通过扩展和使用NGINX作为核心轻松处理负载。
  • 插件:可扩展的体系结构,用于向Kong和API添加功能。

Kong网关的主要功能分析

开源API网关Kong基本介绍和安装验证

 

对于Kong网关本身基于OpenResty构建,因此前面介绍的OpenResty本身的特性也就成了Kong网关的特性,同时Kong网关本身天然和OpenResty融合在一起,不再需要依赖其他的中间件或Web容器进行部署。

其次就是Kong网关本身的插件化开发机制,通过插件对Http接口请求和响应进行拦截,在拦截处就可以实现各种API接口管控和治理的要求。插件通过Lua语言开发,可以动态插拔。

要实现和Kong网关本身的集成和管理本身也很简单,Kong网关提供各类的Rest API接口,可以很方便的实现和Kong网关能力的对接。也就是说你完全可以自己开发要给API管控治理平台,而底层引擎用Kong网关。

支持高可用集群,节点之间的发现采用的gossip协议,当Kong节点启动后,会将自己的IP信息加入到node列表,广播给任何其他的Kong节点,当另一个Kong节点启动后,会去读取数据库中的node列表,并将自己的信息加入到列表中。

另外再来看下Kong API网关提供的一些基本功能:

API注册和服务代理

对于原始的API接口进行注册,并提供服务代理能力,即不同的API接口注册到网关后,网关可以提供一个统一的访问地址和接口。这和ESB总线的代理服务道理是一致的,在服务注册到API网关后,所有的内部服务对外界都是透明的,外部的服务访问必须要通过API网关进行。

该部分对应到Kong API网关的服务注册和Routing部分的功能和能力上。

负载均衡

由于Kong API网关本身底层也是基于Nginx的,因此对应负载均衡的能力实际上是通过底层的Nginx来完成的。要在Kong上面实现负载均衡和API注册代理实际上需要分开为两个步骤进行。

即首先是要配置负载均衡能力,建立Upstream组,并添加多个Target,其次才是进行API注册。

AUTHENTICATION实现

通过OAuth 2.0 Authentication插件实现user端口的用户访问限制。其具体步骤如下:

  • 注册Oauth2插件,配置参考
  • 添加Consumer及Consumer对应的credentials
  • 申请accesstoken并访问,如果不带token访问将被拒绝

安全和访问控制

支持最基本的基于IP的安全访问控制和黑白名单设置。即为相应的端口添加IP Restriction插件扩展,并设置白名单(只有名单内的IP可以访问API)。

这和我们当前ESB的基于IP的访问控制和授权是一个道理。但是我们的ESB更加灵活,多了业务系统这一层,即可以直接对业务系统这层统一进行授权。

如果没有授权,在进行访问的时候将返回Your IP address is not allowed的访问错误。

流量控制-Traffic Control

流量控制在当前Kong网关上,从GitHub上的参考来看,主要是实现了可以基于单位时间内的访问次数进行流量控制,如果超过了这个访问次数,则直接提示流控约束且无法访问的提示。当前的流量控制,暂时不支持基于数据量的流控。

当前的流控暂时没有看到微服务网关常用的熔断操作,即服务并发或服务响应时间大过某个临界值的时候,直接对服务进行熔断和下线操作。

日志Logging实现

通过File-log插件实现对于每次访问日志的获取,需要注意为日志文件写权限,日志格式参考Log Format。具体包括两个步骤。

  • 为端口添加File-log插件,并设置为日志文件路径设为:/tmp/file.log
  • 添加日志插件后,每次访问都会被记录

当前Kong网关日志都是写入到文件系统中,但是这块可以很方便的定制日志插件将日志写入到缓存库,时序数据库或分布式数据库。另外当前的日志LOGGING是没有提供日志查询的前端功能界面的,如果需要的话还需要自己开发对应的日志查询功能。

这个日志功能和我们当前的ESB总线的日志Log完全是类似的。但是我们ESB这块的能力更加强,包括后续的服务运行日志的统计分析和报表查看等。

Kong网关插件说明

开源API网关Kong基本介绍和安装验证

 

从上图可以看到,Kong网关是基于OpenResty应用服务器,OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。而Kong 核心基于OpenResty构建,并且拥有强大的插件扩展功能。

在Http请求到达Kong网关后,转发给后端应用之前,可以通过网关的各种插件对请求进行流量控制,安全,日志等各方面的处理能力。当前Kong的插件分为开源版和社区版,社区版还有更多的定制功能,但是社区版是要收费的。

目前,KONG开源版本一共开放28个插件,如下:

acl、aws-lambda、basic-auth、bot-detection、correlation-id、cors、datadog、file-log、galileo、hmac-auth、http-log、ip-restriction、jwt、key-auth、ldap-auth、loggly、oauth2、rate-limiting、request-size-limiting、request-termination、request-transformer、response-ratelimiting、response-transformer、runscope、statsd、syslog、tcp-log、udp-log。

以上这些插件主要分五大类,Authentication认证,Security安全,Traffic Control流量控制,Analytics & Monitoring分析&监控,Logging日志,其他还有请求报文处理类。插件类似AOP开发中的横切功能,可以灵活的配置进行拦截控制,下面选择一些关键性的插件进行简单的说明。

黑白名单控制能力-ip-restriction

Kong提供的IP黑白名单控制能力还算相当强,从配置项里面可以看到主要可以针对两个维度进行配置,一个是针对所有的API接口还是针对特定的API接口,一个是针对所有的消费方还是特定的某个消费方。

对于IP配置可以是一个区段,也可以是特定的IP地址。但是黑白名单不能同时配置,其次当前没有一个功能是针对某一个系统提供的所有服务都启用黑名单或白名单功能。

日志记录能力-syslog, file-log,http-log

这里主要日志的插件比较多,一个是sysLog在配置后可以直接将Kong产生的日志写入到应用服务器的系统日志文件中。如果配置了file-log则是单独写入到你指定的file文件中。对于http-log则是对于http服务请求,可以详细的记录请求的输入和输出报文信息,但是具体是记录到哪里,需要通过config.http_endpoint配置。

如果需要将API接口调用消息报文日志写入到分布式存储或数据库中,则需要自己定义相应的日志插件来接入写入问题。

熔断插件-request-termination

该插件用来定义指定请求或服务不进行上层服务,而直接返回指定的内容,用来为指定的请求或指定的服务进行熔断。注意Kong的熔断插件感觉是临时对服务的禁用,而不是说当达到某一种监控阈值的时候自动触发熔断。从官方文档的应用场景也可以看到这点。

如果仅仅是这种方式的熔断话,实际上意义并不是很大。但是可用的地方就在于当某个业务系统进行发版部署的时候我们可以对该业务系统或该业务系统所提供的所有服务进行熔断。

限流插件-rate-limiting

Kong当前提供的限流相对来说还是比较弱,即主要是控制某一个API接口服务在单位时间内最多只能够调用多少次,如果超过这个次数那么网关就直接拒绝访问并返回错误提示信息。而在前面我讲限流和流量控制的时候经常会说到,就是限流实际上一个是根据服务调用次数,一个是根据服务调用数据量,需要在这两个方面进行限流。而里面更加重要的反而是数据量的限流,因为大数据量报文往往更加容易造成内存溢出异常。

安全认证类插件

当前Kong网关提供basic-auth,key-auth、ldap-auth,hmac-auth多种认证插件。

Basic-auth基本认证插件,即我们根据用户名和密码来生成一个base64编码,同时将该编码和目标服务绑定,这样在消费目标服务的时候就需要在报文头填写这个Base64编码信息。

Key-auth认证插件则是利用提前预设好的关键字名称,如下面设置的keynote = apices,然后为consumer设置一个key-auth 密钥,假如key-auth=test@keyauth。在请求api的时候,将apikey=test@keyauth,作为一个参数附加到请求url后,或者放置到headers中。

Hmac-auth插件是设置绑定的service和rout,以启动hmac验证。然后在Consumers页面中Hmac credentials of Consumer设置中添加一个username和secret。

请求报文容量限制-request-size-limiting

该插件用于限制请求报文的数据量大小,可以限制单个服务,也可以显示所有的API接口服务。这个实际上是很有用的一个功能,可以防止大消息报文调用导致整个API网关内存溢出。

支持OAuth2.0身份认证-oauth2

Kong网关支持OAuth2.0身份认证,OAuth2.0 协议根据使用不同的适用场景,定义了用于四种授权模式。即Authorization code(授权码模式),Implicit Grant(隐式模式),Resource Owner Password Credentials(密码模式),Client Credentials(客户端模式)。

在四种方式中经常采用的还是授权码这种标准的 Server 授权模式,非常适合 Server 端的 Web 应用。一旦资源的拥有者授权访问他们的数据之后,他们将会被重定向到 Web 应用并在 URL 的查询参数中附带一个授权码(code)。

在客户端里, code 用于请求访问令牌(access_token)。并且该令牌交换的过程是两个服务端之前完成的,防止其他人甚至是资源拥有者本人得到该令牌。另外,在该授权模式下可以通过 refresh_token 来刷新令牌以延长访问授权时间,也是最为复杂的一种方式。

简单转换能力

对于简单转换能力通过request-transformer 和 response transformer两个插件来完成。Kong网关提供对输入和输出报文简单转换的能力,这部分内容后续再详细展开介绍。从当前配置来看,主要是对消息报文提供了Add, Replace,Rename,Append等各种简单操作能力。

Kong网关和其它网关对比

对于开源的Kong网关和其它网关的对比如下。

开源API网关Kong基本介绍和安装验证

 

从上面对比图也可以看到,Kong网关在功能,性能,插件可扩展性各方面都能够更好的满足企业API网关的需求。因此我们也是基于Konga来进一步定制对Kong网关的管控治理平台。

开源API网关Kong基本介绍和安装验证

 

在整个定制中增加了基于DB适配的Http Rest API接口的自动发布,API服务自动化注册,服务日志采集和服务日志查询,常见映射模板定制,接口服务的自动化测试等方面的能力。

Kong网关的安装

开源API网关Kong基本介绍和安装验证

 

在这里以在Centos7下安装Kong2.2版本说明。注意centos7安装Postgresql不再列出,可以参考网上的文章进行安装。

# 安装kong-yum源
$wget https://bintray.com/kong/kong-rpm/rpm -O bintray-kong-kong-rpm.repo
$export major_version=`grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | cut -d "." -f1`
$sed -i -e 's/baseurl.*/&\/centos\/'$major_version''/ bintray-kong-kong-rpm.repo
$mv bintray-kong-kong-rpm.repo /etc/yum.repos.d/
# 清空缓存,重新生成缓存
$yum clean all && yum makecache
# 查看yum源信息
$yum repolist
# 更新yum源
$yum update -y

#############################
#通过yum安装kong
#注意如果出现和openresty不兼容,需要先下载老版本的openresty
$yum install -y kong
# 查看配置文件位置
$whereis kong
$kong: /etc/kong /usr/local/bin/kong /usr/local/kong

#############################
#安装postgresql数据库(略)
#############################

#数据库创建kong用户
$su - postgres
$psql -U postgres
 CREATE USER kong; 
 CREATE DATABASE kong OWNER kong;
 ALTER USER kong with encrypted password 'kong';  
#修改kong,postgresql连接配置
$cp -r /etc/kong/kong.conf.default /etc/kong/kong.conf
$vi /etc/kong/kong.conf
    database = postgres
    pg_host = 172.28.102.62
    pg_port = 5432
    pg_timeout = 5000
    pg_user = kong
    pg_password = kong
    pg_database = kong
    
# 初始化数据库
$kong migrations bootstrap [-c /etc/kong/kong.conf]
注意执行过程如遇到 failed to retrieve PostgreSQL server_version_num: FATAL: Ident authentication failed for user "kong",请给该用户设定密码,并修改postgres的授权方式为MD5,操作如下:
$alter user kong with password  'kong';
# 启动kong
$kong start [-c /etc/to/kong.conf]
#检查 KONG 是否正确运行
$curl -i http://localhost:8001/
或者
$kong health
#停止 KONG
$ kong stop

Kong-Dashboard安装

对于Kong网关本身也提供了Kong Dashboard管理页面,下面再看下Dashboard的安装。在安装Dashboard前需要首先安装node.js,具体如下:

#解压安装包
$wget http://nodejs.org/dist/v8.1.0/node-v8.1.0.tar.gz 
$tar zxvf node-v8.1.0.tar.gz 
$cd node-v8.1.0

#进行编译和安装(注意编译需要很长时间)
$./configure –prefix=/usr/local/node/*
$make
$make install

ln -s /usr/local/node/bin/* /usr/sbin/  

#npm 配置
npm set prefix /usr/local
echo -e '\nexport PATH=/usr/local/lib/node_modules:$PATH' >> ~/.bashrc
source ~/.bashrc

在node.js安装完成后可以安装Dashboard

#从git库克隆
git clone https://github.com/PGBI/kong-dashboard

#安装Kong Dashboard:
npm install -g kong-dashboard@v2

#启动kong-dashboard,注意和老版本有变化
#启动时候可以自己指定端口号如9001
kong-dashboard start -p 9001

#访问kong-dashboard
http://localhost:9001

在启动后再配置Dashboard需要绑定的Kong Server,如下:

开源API网关Kong基本介绍和安装验证

 

访问Dashboard界面如下:

开源API网关Kong基本介绍和安装验证

 

可以看到Dashboard2.0版本的界面已经和V1版本有了较大的变化,在Dashboard上可以实现最近的API接口注册接入,路由,安全管理,限流熔断配置的。由于本身还有另外一个开源的kong网关管理平台Konga,因此后续准备基于Konga来对网关的关键功能进一步做说明和介绍。


 

如何构建更健壮的在线系统_heiyeluren的blog(黑夜路人的开源世界)-CSDN博客

$
0
0

【原创】如何构建更健壮的在线系统

 

作者: 黑夜路人(heiyeluren)

时间:2020年11月

 

说明:本文主要面对PHP为主要开发语言的业务系统,Golang、Java等语言可以学习参考。

 

 

0. 背景

 

Why 为什么要健壮的系统?


1. 为什么测试好好的,上到线上代码一堆bug,一上线就崩溃或者一堆问题?

2. 为什么感觉自己系统做的性能很好,上线后流量一上来就雪崩了?

3.  为什么自己的系统上线以后出问题不知道问题在哪儿,完全无法跟踪?

 

What 构建健壮系统包含哪些方面?


软件系统架构关注:可维护性、可扩展性、健壮性(容灾)

做出健壮的软件和系统的三个方向:

1. 良好的软件系统架构设计

2. 编程最佳实践和通用原则

3. 个人专业软素质

 

How 构建健壮系统执行细节?



1、系统架构:网络拓扑是什么、用什么存储、用什么缓存、整个数据流向是如何、那个核心服务采用那个开源软件支撑、用什么编程语言来构建整个软件连接各个服务、整个系统如何分层?

2、系统设计:采用什么编程语言、编程语言使用什么编程框架或中间件、使用什么设计模式来构建代码、中间代码如何分层?

3、编程实现:编程语言用什么框架、有什么规范、编程语言需要注意哪些细节、有哪些技巧、那些编程原则?

 


 

一、系统架构与设计遵循哪些关键原则?

 

0. 用架构师的视角来思考问题

程序员视角更多考虑的是我如何快速完成这个项目,架构师视角是不仅是我完成整个项目,更多需要思考整个架构是否清晰、是否可维护、是否可扩展、可靠性稳定性如何,整个技术框架和各种体系选型是否方便容易开发维护;整个思考维度和视角是完全不一样的。程序员视角是执行层面具体编码的视角,架构师视角是设计师的视角,会更宏观,想的更远。

(比如编程语言选型 PHP vs Golang、Java vs Golang、C++ vs Rust等等)

 

1. 服务架构链路要清晰

整个服务架构包含:接入层(网关、负载均衡)、应用层(PHP程序等)、服务层(微服务接口等)、存储层(DB、缓存、检索ES等)、离线计算层(不一定包含,一般会以服务层或存储层出现),服务互相不要混了,各干各的活;

服务链路要清晰,每一层各司其职:

 

2. 每个服务都需要考虑灾备或分布式,不能出现单点架构(持续进化)

比如mysql不能只有1个,最少考虑 master/slave架构,保持数据不丢,访问不断,数据量大以后是否做分布式存储等等,redis等同样;后端微服务层同样必须多台服务(通过ServiceMesh等调度,或者是etcd/zookeeper等服务发现等方式);

 

分布式进化:

初级阶段:

 

中级阶段:

 

高级阶段:

 

3. 每个服务都必须考虑最优的技术选型(最佳实践)

比如PHP框架选择,语言选择都是稳定成熟可靠(高性能API选Swoole、Golang等,业务系统考虑Laravel、Symfony、Yii等主流框架);比如微服务框架,远程访问接口协议(TCP/UDP/QUIC/HTTP2/HTTP3等),信息内容格式可靠(json/protobuf/yaml/toml等);选择的扩展稳定可靠(PHP各个可靠扩展,具备久经考验,超时、日志记录等基础特性);

一些优秀开源软件推荐:(个人最佳实践推荐)

 

4. 服务必须考虑熔断降级方案

在大流量下,如何保证最核心服务的运转(比如在线课堂中是老师讲课直播重要,还是弹幕或者点赞重要),需要把服务分层,切分一级核心服务、二级重要服务、三级可熔断服务等等区分,还需要有对应的预案;(防火/防火演练等);需要对应的系统支持,比如API网关的选择使用。(OpenResty/Kong/APISIX等,单核2W/qps,4核6W/qps)

 

5. 运维部署回滚监控等系统需要快速高效



        整个代码层次结构,编译上线整个流程,如何是保证高效率可靠的;上线方便,回滚也方便,或者回滚到任何一个版本必须可靠;日志监控、系统报警等等。(常规运维上线系统  Jenkins/Nagios/Zabbix/Ganglia/Grafana/OpenFalcon/Nightingale)

监控系统价值:

 

监控系统工作原理:

监控系统选择:

 

6. 编写的接口和前后端联合调试要方便快捷

接口可靠性测试必须充分,并且善于使用好的工具(比如Filder/Postman/SoapUI等),并且对应接口文档清晰,最好是能够通过一些工具生成好API文档(APIJson/Swagger/Eolinker),大家按照对应约定格式协议进行程序开发联调等;

 

 

 

7. 让整个系统完全可监控可追踪

比如对应的trace系统(包含trace_id),把从接入层、应用层、服务层、存储层等都能够串联起来,每个环节出现的问题都可追踪,快速定位问题找到bug或者服务瓶颈短板;也能够了解整个系统运行情况和细节。(比如一些日志采集系统 OpenResty/Filebeat/Flume/LogStash/ELK)

 

8. 系统中的每个细节都是需要可以量化的,不能是模糊不明确的

比如单个服务的QPS能力(预计流量需要多少服务器)、并发连接数(系统设置、系统承载连接)、单个进程内存占用、线程数、网络之间访问延迟时间(服务器之间延迟、机房到机房的延迟、客户端到服务器的延迟)、各种硬件性能参数(磁盘IO、服务器网卡吞吐量等)。

比如:【QPS计算PV和机器的方式】

 

原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)

机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器

 

QPS统计方式

QPS = 总请求数 / ( 进程总数 * 请求时间 )

QPS: 单个进程每秒请求服务器的成功次数

 

单台服务器每天PV计算

公式1:每天总PV = QPS * 3600 * 6

公式2:每天总PV = QPS * 3600 * 8

 

问:每天300w PV 的在单台机器上,这台机器需要多少QPS?

答:( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)

 

问:如果一台机器的QPS是58,需要几台机器来支持?

服务器数量 = ceil( 每天总PV / 单台服务器每天总PV )

答:139 / 58 = 3

 

PS: 在实际情况中,会把这个考虑的更多一点,就是把QPS再往多了调一调,以防万一

 

 

9. 让你的应用无状态化、容器化、微服务化


让你的应用可以做到:去中心化、原子化、语言无依赖、独立自治、快速组合、自动部署、快速扩容,采用微服务+容器化来解决。

在面对大并发量请求情况下,在寻求系统资源的状态利用场景,大部分考虑的都是横向扩展,简单说就加机器解决。在docker和k8s的新的容器化时代,横向扩展最好的方法是快速扩充新的应用运行容器;阻碍我们横向扩展的最大的阻碍就是“有状态”,有状态就是有很多应用会存储私有的东西在应用运行的内存、磁盘中,而不是采用通用的分布式缓存、分布式存储解决方案,这会导致我们的应用在容器化的情况下无法快速扩容,所以我们的应用需要“去有状态化”,让我们的应用全部“无状态化”。

微服务化的逻辑是让的每个服务可以独立运行,比如说用户中心系统对外提供的不是代码级别的API,而是基于RESTfu或者gRPC协议的远程一个服务多个接口,这个服务或接口核心用来解决把整个用户中心服务变成独立服务,谁都可以调用,并且这个服务本身不会对内外部有太多的耦合和依赖,对外暴露的也只是一个个独立的远程接口而已。

尽量把我们的关键服务可以抽象成为一个个微服务,虽然微服务会增加网络调用的成本,但是每个服务之间的互相依赖性等等都降低了,并且通过容器技术,可以把单给微服务快速的横向扩展部署增加性能,虽然不是银弹,也是一个非常好的解决方案。

基于容器的微服务化以后,整个业务系统从开发、测试、发布、线上运维、扩展 等多个方面都比较简单了,可以完全依赖于各种自动半自动化工具完成,整个人工干预参加的成分大幅减少。

 

从单体服务到微服务:

 

微服务后应用架构变化:

 

容器简单工作原理:

/*简单容器底层机制实现模拟演示
  说明:主要利用Linux的Namespace机制来实现,linux系统中unshare命令效果类似
  Docker 调用机制是: Docker -> libcontainer(like lxc) -> cgroup -> namespace
  Code by Black 2020.10.10
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/sched.h>
#include <sched.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = { "/bin/bash",  NULL };

//容器进行运行的程序主函数
int container_main(void *args)
{
    printf("容器进程开始. \n");
    sethostname("black-container", 16);
    //替换当前进程ps指令读取proc环节
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
}

int main(int args, char *argv[])
{
    printf("======Linux 容器功能简单实现 ======\n");
    printf("======code by Black 2020.10 ======\n\n");
    printf("正常主进程开始\n");
    // clone 容器进程: hostname/消息通信/进程id 都独立 (CLONE_NEWUSER未实现)
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
        SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET, NULL);
    // 等待容器进程结束
    waitpid(container_pid, NULL, 0);
    //恢复 /proc 下的内容
    system("mount -t proc proc /proc");
    printf("主要进程结束\n");
    return 0;
}

 

代码执行效果:

 

Docker工作机制:

 

K8s工作机制:

 

微服务+容器化后的架构:

容器+微服务运维部署架构:

 

 

架构设计结束语:


在架构设计中,没有最好的架构,只有适合业务的架构,只有持续优化进步的架构。

 


 

二、系统程序实现里哪些关注原则

 

0. 充分理解你的业务需求

保证理解业务后,整个程序设计符合需求或者未来几个月可以扩展,既不做过度设计,也不做各种临时硬编码。

 

1. 代码核心原则:KISS(Keep it simple,stupid)

来自于Unix编程艺术,你的东西必须足够简单足够愚蠢,好处非常多,比如容易读懂,容易维护交接,出问题容易追查等等。

因为,长期来看复杂的东西都是没有生命力的。(x86 vs ARM / 微型服务器 vs 大型机 / 北欧简约风 vs 欧洲皇室风 / 现在服装 vs 汉服)

 

2. 遵守编码规范,代码设计通用灵活

学会通过用函数和类进行封装(高内聚、低耦合)、如何定义函数,缩进方式,返回参数定义,注释如何定义、减少硬编码(通过配置、数据库存储变量来解决)。

 

3. 设计模式和代码结构需要清晰


比如我们常规使用的MVC设计模式,为了就是把各个层次代码区分开。(M干好数据读取或者接口访问的事儿,C干好变量收发基本教研,V干好模板渲染或者api接口输出;M可以拆分成为:DAO数据访问层和Service某服务提供层);

比如一个主流的MVC层结构图:

 

 

4. 程序中一定要写日志,代码日志要记录清晰


(Info、Debug、Waring、Trace等等,调用统一的日志库,不要害怕多写日志)

//程序里关键的日志都要记录(Debug/Notice/Warning/Error 等信息可以打印,warning/error 信息是一定要打印的
SeasLog::debug('TRACE_ID:{traceId}; this is a {userName} debug',array('{traceId}'=>9527, '{userName}' => 'Black'));
SeasLog::notice('TRACE_ID:{traceId}; this is a notice msg', array('{traceId}'=>9527));
SeasLog::warning('TRACE_ID:{traceId}; this is a warning', array('{traceId}'=>9527));
SeasLog::error('TRACE_ID:{traceId}; a error log', array('{traceId}'=>9527));
  •  

 

5. 稳健性编程小技巧(个人最佳实践)


a. 代码里尽量不要使用else(超级推荐,Unix编程艺术书籍推荐方法)

//获取一个整形的值 (使用else,共12行)
function getIntValue($val) {
    if ( $val != "" ) {
    $ret = (int)$val;
        if ($ret != 0 ) {
            return $ret;
        } else {
            return false;
        }
    } else {
        return false;
    }
}
//获取一个整形的值(不使用else,共10行)
function getIntValue($val) {
    if ( $val == "" ) {
        return false;
    }
    $ret = (int)$val;
    if ($ret == 0 ) {
        return false;
    } 
    return $ret;
}
  •  

b. 所有的循环必须有结束条件或约定,并且不会不可控

c. 不要申请超级大的变量或内存造成资源浪费

d. 无论静态还是动态语言,内存或对象使用完以后尽量及时释放

e. 输入数据务必要校验,用户输入数据必须不可信。

f. 尽量不要使用异步回调的方式(容易混乱,对js和nodejs的鄙视,对协程机制的尊敬)

 

6. 所有内外部访问都必须有超时机制:保证不连锁反应雪崩


超时是保证我们业务不会连带雪崩的很关键的地方,比如我们在访问后端资源或外部服务一定要经常使用超时操作。

超时细化下来,一般会包括很多类型:连接超时、读超时、写超时、通用超时 等等区分;一般超时粒度大部分都是秒为单位,对于时间敏感业务都是毫秒为单位,建议以毫秒(ms)为单位的超时更可靠,但是很多服务没有提供这类超时操作接口。

 

常用使用超时的场景:

 

Swoole框架里控制超时

//Swoole 里通用超时设置(针对TCP协议情况,包含通用超时、连接超时、读写超时)
Co::set([
    'socket_timeout' => 5,'socket_connect_timeout' => 1,'socket_read_timeout' => 1,'socket_write_timeout' => 1,
]);

//Swoole 4.x协程方式访问MySQL
Co\run(function () {
    $swoole_mysql = new Swoole\Coroutine\MySQL();
    $swoole_mysql->connect(['host'     => '127.0.0.1','port'     => 3306,'user'     => 'user','password' => 'pass','database' => 'test','timeout'     => '1',
    ]);
    $res = $swoole_mysql->query('select sleep(1)');
    var_dump($res);
});

//Swoole 4.x协程方式访问Redis
Co\run(function () {
    $redis = new Swoole\Coroutine\Redis();
    $redis->setOptions('connect_timeout'    => '1','timeout'    => '1',
    );
    $redis->connect('127.0.0.1', 6379);
    $val = $redis->get('key');
});

 

通过cURL接口访问HTTP服务超时设置

  • function http_call($url)
       $ch = curl_init($url);
       curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
       //注意,毫秒超时一定要设置这个
       curl_setopt($ch, CURLOPT_NOSIGNAL, 1);    
       //超时毫秒,cURL 7.16.2中被加入。从PHP 5.2.3起可使用
       curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);  
       $data = curl_exec($ch);
       $curl_errno = curl_errno($ch);
      $curl_error = curl_error($ch);
      curl_close($ch);
    }
    http_call('http://example.com')

     

访问MySQL超时处理(非Swoole情况),调用mysqli扩展方式:

mysql 默认在扩展层面没有把很多超时操作暴露给前台,所以需要用一些隐藏方式:

<?php
//自己定义读写超时常量
if (!defined('MYSQL_OPT_READ_TIMEOUT')) define('MYSQL_OPT_READ_TIMEOUT',  11);
if (!defined('MYSQL_OPT_WRITE_TIMEOUT')) define('MYSQL_OPT_WRITE_TIMEOUT', 12);

//设置超时
$mysqli = mysqli_init();
$mysqli->options(MYSQL_OPT_READ_TIMEOUT, 3); //读超时,没办法超过3秒
$mysqli->options(MYSQL_OPT_WRITE_TIMEOUT, 1); //写超时,最小可设置为1秒
 
//连接数据库
$mysqli->real_connect("localhost", "root", "root", "test");
//执行查询 sleep 1秒不超时
printf("Host information: %s/n", $mysqli->host_info);

//执行查询 sleep 1秒不超时
printf("Host information: %s/n", $mysqli->host_info);
if (!($res=$mysqli->query('select sleep(1)'))) {
    echo "query1 error: ". $mysqli->error ."/n";
} else {
    echo "Query1: query success/n";
}

//执行查询 sleep 9秒会超时,处理3秒超时情况,因为mysql自己会重试3次
if (!($res=$mysqli->query('select sleep(9)'))) {
    echo "query2 error: ". $mysqli->error ."/n";
} else {
    echo "Query2: query success/n";
}

$mysqli->close();
?>

目前大部分主流框架都没有提供mysql超时配置,包括Laravel、Symfony、Yii 等框架都没有提供,因为都是基于底层mysqli或pdo等扩展。

 

Socket或流处理超时:

// ## fsockopen访问HTTP ## 
$timeout = 5; //超时5秒
$fp = fsockopen("example.com", 80, $errno, $errstr, $timeout); 
if ($fp) { 
        fwrite($fp, "GET / HTTP/1.0\r\n"); 
        fwrite($fp, "Host: example.com\r\n"); 
        fwrite($fp, "Connection: Close\r\n\r\n"); 
        stream_set_blocking($fp, true);   //重要,设置为非阻塞模式
        stream_set_timeout($fp,$timeout);   //设置超时
        $info = stream_get_meta_data($fp); 
        while ((!feof($fp)) && (!$info['timed_out'])) { 
                $data .= fgets($fp, 4096); 
                $info = stream_get_meta_data($fp); 
                ob_flush; 
                flush(); 
        } 
        if ($info['timed_out']) { 
                echo "Connection Timed Out!"; 
        } 
       else { 
                echo $data; 
        } 
}

 

通过上下文环境处理超时:

//fopen & file_get_contents 访问HTTP超时控制
//设置超时和压入上下文环境
$timeout = array(
    'http' => array('timeout' => 5 //设置一个超时时间,单位为秒
    )
);
$ctx = stream_context_create($timeout);
//fopen
if ($fp = fopen("http://example.com/", "r", false, $ctx)) {
  while( $c = fread($fp, 8192)) {
    echo $c;
  }
  fclose($fp);
$text = file_get_contents("http://example.com/", 0, $ctx);
echo $text;

 

延伸阅读: https://blog.csdn.net/heiyeshuwu/article/details/7841366

 

 

7. 成熟稳定的SQL语言使用习惯和技巧


a. 所有SQL语句都必须有约束条件

常规习惯:

SELECT uid,uname,email,gender FROM user WHERE uid = '9527'

好习惯,增加对应的WHERE条件和LIMIT限制

SELECT uid,uname,email,gender FROM user WHERE uid = '9527' LIMIT 1

b. SQL查询里抽取字段中明确需要抽取的字段

常规习惯:

SELECT * FROM user WHERE uid = '9527'

良好习惯,需要什么字段提取什么字段:

SELECT uid,uname,email,gender FROM user WHERE uid = '9527' WHERE 1 LIMIT 1

主要受约束的是一些mysql的配置相关,包括:max_allowed_packet 之类的会超过限制或者是把网卡带宽打满;

c. 熟知各种SQL操作的最佳实践技巧,包括不限于:常用字段建立索引(单表不超过6个)、尽量减少OR、尽量减少连表查询、尽量不要SQL语句中使用函数(datetime之类)、使用exists代替in、使用explain观察SQL运行情况等等。

 

延伸学习: https://blog.csdn.net/jie_liang/article/details/77340905

 

 

 

 

8. 开发中时刻要记得代码安全


我们系统在完成业务开发的基础上,还需要考虑代码安全问题,大部分时候安全和方便中间会存在冲突,但是因为一个不安全的系统,对业务的伤害是巨大的,轻则被非法用户“薅羊毛”,重则服务器被攻陷,整个数据遭到泄露或者遭受恶意损失。

我们常见遇到的安全问题包括:SQL注入、XSS、CSRF、URL跳转漏洞、文件上传下载漏洞等等,很多在我们只是关注业务实现不关注安全的时候问题都会出现。

 

安全问题

常见解决方法

SQL注入

输入参数校验:intval、is_numeric、htmlspecialchars、trim

数据入库格式化:PDO::prepare、mysqli_real_escape_string

XSS

过滤危险的HTML/JS等输入参数和显示内容,在js代码中对HTML做相应编码,多使用正则或者 htmlspecialchars、htmlspecialchars_decode 等处理函数;

CSRF

Client和Server交互采用token校验操作,敏感操作判断来源IP等

URL跳转

跳转目标URL必须进行校验,或者采用URL白名单机制

文件上传下载

限制文件上传大小(upload_max_filesize / post_max_size配置)

采用可靠上传的组件(Flash/JS组件);检查上传文件类型(扩展名+内容头),控制服务器端目录和文件访问权限

配置安全

PHP环境配置做好安全配置,屏蔽敏感函数:eval / exec / system / get_included_files ;

敏感配置关闭:register_globals / allow_url_fopen / allow_url_include/ safe_mode / magic_quotes 等;

资源管控配置: max_execution_time / max_input_time /memory_limit / open_basedir / upload_tmp_dir 等

数据库安全

数据库访问不能用root账户,不同库不同的访问用户,读写账户分离;

敏感库表需要特殊处理(比如用户库表密码库表等加盐存储);

服务器安全

DDoS攻击防范(流量清洗、黑白名单)、服务安全运行权限(非root运行php进程)、服务器之间访问白名单机制

 


9. 调优后端服务的性能配置


开发语言只是一个粘合剂,整个过程是把前端用户操作进行逻辑处理,粘合后端存储和各种RPC服务的数据进行展现输出。除了你的PHP服务或代码、SQL执行效率高,同样也需要考虑前后端各个服务的性能是非常优化高性能的。

我把一些关键的Linux系统到各个各个服务一些关键性能相关选项简单罗列一下。

 

服务类型

核心性能影响配置

Linux系统

1. 并发文件描述符

永久修改: /etc/security/limits.conf

* soft nofile 1000000

* hard nofile 1000000

Session临时修改:ulimit -SHn 1000000

3. 进程数量限制

永久修改:/etc/security/limits.d/20-nproc.conf

*          soft    nproc  4096

root     soft    nproc  unlimited

4. 文件句柄数量

临时修改:echo 1000000 > /proc/sys/fs/file-max

永久修改:echo "fs.file-max = 1000000" >>/etc/sysctl.conf

5. 网络TCP选项,关注 *somaxconn/*backlog/*mem*系列/*time*系列等等

6. 关闭SWAP交换分区(服务器卡死元凶):

echo "vm.swappiness = 0">> /etc/sysctl.conf

Nginx/OpenResty

Nginx Worker性能:

worker_processes 4;

worker_cpu_affinity 01 10 01 10;

worker_rlimit_nofile 10240;

worker_connections 10240;

 

Nginx网络性能:

use epoll;

sendfile on;

tcp_nopush on;

tcp_nodelay on;

keepalive_timeout 30;

proxy_connect_timeout 10;

 

Nginx缓存配置:

fastcgi_buffer_size 64k;

client_max_body_size 300m;

client_header_buffer_size 4k;

open_file_cache max=65535 inactive=60s;

open_file_cache_valid 80s;

proxy_buffer_size 256k;

proxy_buffers 4 256k;

proxy_cache*

PHP/FPM

listen.backlog = -1 #backlog数,-1表示无限制

rlimit_files = 1024 #设置文件打开描述符的rlimit限制

rlimit_core = unlimited #生成core dump文件限制,受限于linux系统

pm.max_children = 256 #子进程最大数

pm.max_requests = 1000 #设置每个子进程重生之前服务的请求数

request_terminate_timeout = 0 #设置单个请求的超时中止时间

request_slowlog_timeout = 10s #当一个请求该设置的超时时间后

MySQL/MariaDB

MySQL服务选项:

wait_timeout=1800

max_connections=3000

max_user_connections=800

thread_cache_size=64

skip-name-resolve = 1

open_tables=512

max_allowed_packet = 64M

 

MySQL性能选项:

innodb_page_size = 8K #脏页大小

innodb_buffer_pool_size = 10G #建议设置为内存80%

innodb_log_buffer_size = 20M #日志缓存大小

innodb_flush_log_at_trx_commit = 0 #事务日志提交方式,设置为0比较合适

innodb_lock_wait_timeout = 30 #锁获取超时等待时间

innodb_io_capacity = 2000 #刷脏页的频次默认200,高一些会让io曲线稳

Redis

maxmemory 5000mb #最大内存占用

maxmemory-policy allkeys-lru #达到内存占用后淘汰策略,存在热点数据,淘汰不咋访问的

maxclients 1000 #客户端并发连接数

timeout 150 #客户端超时时间

tcp-keepalive 150 #向客户端发送tcp_ack探测存活

rdbcompression no #磁盘镜像压缩,开启占用cpu

rdbchecksum no #存储快照后crc64算法校验,增加10%cpu占用

vm-enabled no #不做数据交换

 


延伸阅读: https://blog.csdn.net/heiyeshuwu/article/details/45692407

 

 

 

 

10. 善用常用服务的系统监测工具

 

 

 

为了快速的监控我们的线上服务情况,需要能够熟练使用常用的性能监控的各种工具和指标。

服务类型

常用工具或指令

Linux

top、vmstat、iostat、netstat、sar、nmon、dstat、iftop、free、df/du、tcpdump

PHP

Xdebug、xhprof、Fiery、第三方APM工具

MySQL

MySQL主要指令:

show processlist

show global variables like '%xxx%'

show master status;

show slave status;

show status like '%xx%'

 

细节查询:

查看连接数:SHOW STATUS LIKE 'Thread_%';

查看执行事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

查看锁定事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

每秒查询QPS:SHOW GLOBAL STATUS LIKE 'Questions'; #QPS = Questions / Uptime

每秒事务TPS:SHOW GLOBAL STATUS LIKE 'Com_%'; #TPS = (Com_commit + Com_rollback) / Uptime

InnoDB Buffer命中率:show status like 'innodb_buffer_pool_read%'; #innodb_buffer_read_hits = (1 - innodb_buffer_pool_reads / innodb_buffer_pool_read_requests) * 100%

Redis

查看redis服务的各项状态:info / info stats / info CPU / info Keyspace

实时监控redis所有命令:monitor

查看redis慢日志:slowlog get 128

性能测试监控:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

 

 

编码健壮性结束语:

在程序开发中,没有最优质的代码,只有解决业务问题,良好设计和规范,持续精进的程序。

 


 

三、健壮代码有哪些通用原则

 

1、模块性原则:写简单的,通过干净的接口可被连接的部件。(比如类、函数,高内聚低耦合)

2、清楚原则:清楚要比小聪明好。(代码中注释需要清晰明确,最好有历史迭代,不要耍小聪明,或者用一些奇怪的实现算法并且没注释)

3、合并原则:设计能被其它程序连接的程序。(提供好输入输出参数,或者设计好的openapi,尽量让程序可以复用)

4、简单原则:设计要简单;只有当你需要的时候,增加复杂性。(每个函数类要简单明确,不要太冗长)

5、 健壮性原则:健壮性是透明和简单的追随者。(透明+简单了,健壮就来了)

6、沉默补救原则:当一个程序没有异常的时候就只是记录,减少干扰或者啥都不说;当你必须失败的时候,尽可能快的吵闹地失败。(失败了一定要明确清晰的提示形式,包括错误代码,错误原因的信息,不要悄无声息)

7、经济原则:程序员的时间是宝贵的;优先机器时间节约它。(能够用内存+CPU搞定,不要过多纠结在算法上)

8、产生原则:避免手工堆砌;当你可能的时候,编写可以写程序的程序。(用程序来帮你实现重复的事儿)

9、优化原则:在雕琢之前先有原型;在你优化它之前,先让他可以运行。(先完成,再完美)

10、可扩展原则:为将来做设计,因为它可能比你认为来的要快。(尽量考虑你这个代码2,3,5年后才会重构,为未来负责)

 


 

四、个人软素质:细心、认真、谨慎、精进、专业

 

1、细心:写完代码都需要重新Review一遍,变量名是否正确,变量是否初始化,每个SQL语句是否性能高超或者不会导致超时死锁;

2、认真:每个函数是否都自己校验过输入输出是满足预期的;条件允许,是否核心函数都具备单元测试代码;

3、谨慎:不要相信任何外部输入的数据,包括数据库、文件、缓存、用户HTTP提交各种变量,都需要严格校验和过滤。

4、精进:不要惧怕别人说你代码烂,必须能够持续被人吐槽下优化,在在我革新下优化;要学习别人优秀的代码设计思想和代码风格,持续进步。

我持续三年优化过的一个代码:(大约十四年前第一版)

 

5. 专业:专业是一种工作态度,也是一种人生态度;代码要专业、架构要专业、变量名要专业、文档要专业、跟别人发工作消息邮件要专业、开会要专业、沟通要专业,等等,专业要伴随自己一生。

我在十三年前写的代码:

 

 


本文结束语:


愿每一位阅读本文的技术伙伴,都能够成为优秀的程序员,优秀的架构师。

 


作者简介:

​谢华亮,网名“黑夜路人”,目前是学而思网校技术委员会主席;作者系CSDN博客技术专家,互联网后端开发架构师,多年PHP/Golang/C 开发者,LNMP技术栈/分布式/高并发等技术爱好者,国内开源技术爱好和布道者。

 

个人博客: http://blog.csdn.net/heiyeshuwu

新浪微博: http://weibo.com/heiyeluren

微信公众号:黑夜路人

 

 

 

不想加班开发管理后台了,试试这个 Java 开源项目吧!

$
0
0

本文适合有 Java 基础并了解 SpringBoot 框架的同学

本文作者:HelloGitHub- 嘉文

这里是 HelloGitHub 推出的 《讲解开源项目》系列,今天给大家带来一款开源的 Java 后台管理框架—— EL-ADMIN

它是一个基于 Spring Boot、Spring Data JPA、JWT、Spring Security、Redis、Vue、Element-UI 的前后端分离的后台管理系统。如果你想要学习上面这些框架的使用、增加实战经验、学习代码规范,又或者找接私活开发的脚手架,提高赚钱的效率,EL-ADMIN 都能满足你的需求。

项目地址:https://github.com/elunez/eladmin

简单配置后可直接运行,极大地提高了你在开发管理后台的效率,从而有更多时间逛 HelloGitHub。不信?跟着本文,让你 5 分钟跑起来一个功能丰富的管理后台项目。

一、快速开始

1.1 环境准备

后端环境:

  1. JDK 1.8+
  2. Maven 3.0+
  3. MYSQL 5.5+
  4. Redis 3.0+

前端环境:

  1. Node v10+

开发工具

  1. IDE:IDEA
  2. MySQL 可视化:Navicat

1.2 快速开始

(1)clone 项目

  • GitHub:
# 后端项目
git clone https://github.com/elunez/eladmin.git
# 前端项目
git clone https://github.com/elunez/eladmin-web.git
复制代码
  • Gitee(国内访问网速较快):
# 后端项目
git clone https://gitee.com/elunez/eladmin.git
# 前端项目
git clone https://gitee.com/elunez/eladmin-web.git
复制代码

(2)导入 MySQL 数据库

打开 Navicat,连接 MySQL,新建数据库 eladmin,设置字符集和排序规则如下图所示:

右键你新建的数据库,选择「运行 SQL 文件」,选择 clone 的下来的 eladmin/sql/eladmin.sql文件。

导入 SQL 数据成功,一共有 27 张表。

(3)运行后端项目

配置文件:使用 IDEA 打开后端项目 eladmin,等待 maven 下载结束后,打开配置文件 yml 配置数据库,改成你机器上的 MySQL 或者 Redis 的 IP、端口和密码。

MySQL 默认:

  • url:localhost:3306
  • 密码:123456

Redis 默认:

  • url:localhost:6379
  • 密码:无

运行后端项目:

(4)运行前端项目

在 clone 的前端项目「eladmin-web」文件夹下打开 cmd 命令行窗口。

# 安装
npm install
# 运行 
npm run dev
复制代码

1.3 效果预览

运行成功会自动弹出下面界面:

二、可能遇到的坑

2.1 端口占用

报错信息:

我们这里使用其他端口,修改配置文件 application.yml端口为 8001:

server:
  port: 8001
复制代码

注意:修改后端端口后,需要修改前端对应的请求接口。

2.2 npm install安装缓慢或者安装失败

设置淘宝镜像源来解决

npm config set registry https://registry.npm.taobao.org
复制代码

检查是否设置成功

npm config get registry
复制代码

三、如何学习这个项目

3.1 基本用途

EL-ADMIN 是 一个简单且易上手的 Spring boot 后台管理框架,你可以通过它学习到:

  • Spring Boot、Spring Data JPA、JWT、Spring Security、Redis、Vue、Element-UI 等框架的使用
  • 增加项目经验
  • 学习代码规范
  • 作为接私活开发的「脚手架」,提高赚钱效率(比如 项目代码生成模块可以直接生成前后端代码)

3.2 深入学习

了解完 基本用途快速开始之后,你想要更深层的学习这个开源项目的话,可以:

  1. 了解技术框架:如果你没有了解 Spring Boot、Spring Data JPA、JWT、Spring Security、Redis、Vue、Element-UI 这些技术框架你应该先去了解它们,这是项目基石,不会的请网上搜索,因为 搜索是基本技能
  2. 了解项目结构: 了解各个模块和文件的主要作用
  3. 了解项目功能:了解项目实现的功能(比如说 权限验证),这个项目使用了什么技术来实现「权限验证」(JWT 和 Spring Security),你需要在源代码里面将这个功能实现的大致流程看懂(你可以找到功能入口一层一层点进去或者 Debug 或者 自己写一个类似功能)

3.3 怎么学习

学习一个开源项目,你应该首先要明确的是 你学习开源项目的目的,因为很多开源项目(比如 EL-ADMIN)代码量还是比较大的,不同学习目的应该有不同的学习深度,而不是仅限于快速开始然后 无从下手

四、最后

很多同学学习 SpringBoot 的相关课程之后并不知道应该继续学习什么、做些什么实战项目。因为我就有类似的经历 ,这里我的个人建议: 学习不是目的,使用起来才是,学了就要用,学了却不去用,还不如不学,因为不久就会忘的一干二净。如果只是学习却不巩固你的大脑就会将其忘记,而「实践」就是巩固的最佳方法,希望你能在实践中体验设计程序的快乐!

至此,感谢您的阅读。项目涉及到的知识点比较多,如果你时间不够充裕,建议选择感兴趣或者合适的功能去学习,有基础建议直接阅读 官方文档

如果你有感兴趣的开源项目,但是不知道怎么上手,欢迎留言告诉我们。《讲解开源项目系列》就是帮助大家快速上手开源项目,所以你的需求就是我们的使命~留言吧!

五、参考

  1. 官方文档

  2. 如何学习开源项目

热门人工智能开源工具(框架)

$
0
0

分享一些热门的人工智能开源工具/框架。

图片

TensorFlow

图片

TensorFlow™是一个开源软件库,最初由Google Brain Team的研究人员和工程师开发。TensorFlow使用数据流图进行数值计算。图中的节点表示数学运算,边表示它们之间通信的多维数据数组(张量)。其架构灵活,你可以使用单个API将计算部署到桌面、服务器或移动设备中的一个或多个CPU或GPU。

TensorFlow提供了多种API。最低级别的API——TensorFlow Core——提供了完整的编程控制。高级API则建立在TensorFlow Core的顶部。这些更高级别的API通常比TensorFlow Core更容易学习和使用。此外,更高级别的API使得重复性的任务在不同的用户之间变得更容易、更一致。一个高级API就像tf.estimator,可以帮助您管理数据集、评估器、训练和推理。

TensorFlow中的数据中心单位是张量。一个张量由一组形成任意数量维数组的原始值组成。张量的阶就是它的维数。

一些使用Tensorflow的Google应用有:

1.RankBrain:在www.google.com上大规模部署用于搜索排名的深度神经网络。

2.Inception图像分类模型:基准模型和对高度精确的计算机视觉模型的后续研究,它是在获得2014年Imagenet图像分类挑战赛的模型基础之上进行构建的。

3.SmartReply:可自动生成电子邮件响应的Deep LSTM模型。

4.Massively Multitask Networks for Drug Discovery:Google与斯坦福大学合作的识别有效候选药的的深度神经网络模型。

5.用于OCR的设备级计算机视觉:基于设备级的计算机视觉模型实现光学字符识别,进行实时翻译。

PyTorch

Torch是一个开源机器学习库、科学计算框架和基于Lua编程语言的脚本语言。

图片

功能

1.一个强大的 n 维数组

2.有很多索引、切片、转换的程序。

3.可使用 LuaJIT编写简单的C扩展。

4.线性代数程序

5.神经网络和基于能量的模型

6.数字优化程序

7.GPU支持,更加快速和高效

8.可嵌入,带有 iOS 和 Android 的后台端口

Torch供 Facebook 人工智能研究小组、 IBM、 Yandex 和 Idiap 研究所使用。现在,它已经扩展到 Android 和 iOS系统上,研究人员也使用Torch来构建硬件实现数据流。

Pytorch是一个Python的开源机器学习库,用于自然语言处理等应用,主要由 Facebook 的人工智能研究小组开发,Uber 的概率编程软件"Pyro"就是在Pytorch上创建的。

Caffe

Caffe是一种清晰而高效的深度学习框架。

图片

Caffe最初由杨庆佳在加州大学伯克利分校读博期间发起,后来由伯克利AI研究公司(BAIR)和社区贡献者联合开发。它主要专注于用于计算机视觉应用的卷积神经网络。对于计算机视觉相关的任务来说,Caffe是一个不错且较为流行的选择,您可以在Caffe Model Zoo上注册,下载很多已经成功建模的模型,直接用于开发。

优点

1.Expressive架构鼓励实用和创新。用配置定义的模型和优化,而不需要硬编码。通过设置单个标志在GPU机器上进行训练,然后部署聚类或移动设备,实现CPU和GPU之间的切换。

2.可扩展代码更有助于开发。在Caffe开发好的的第一年,就有1,000多个开发者分享了出去,对其做了重大贡献。

3.Caffe的高速使理论实验和实际应用得到了完美的结合。Caffe使用单个NVIDIA K40 GPU每天可处理超过6000万张图像。

4.社区:Caffe已经为视觉、语音和多媒体领域的学术研究项目,启动原型,甚至大规模工业应用提供支持。

Apache SystemML

利用大数据进行机器学习的最佳开源工具。

SystemML是由IBM创建的机器学习技术,是Apache中的顶级项目之一,它是一个灵活、可扩展的机器学习系统。SystemML的重要特点如下:

1.使用类R和类Python语言定制算法。

2.有多种执行模式,包括Spark MLContext、Spark Batch、Hadoop Batch、Standalone和JMLC(Java机器学习连接器)。

3.基于数据和聚类特性的自动优化,保证了算法的高效率和可扩展性。

4.将SystemML视为机器学习的结构化查询语言SQL。SystemML的最新版本(1.0.0)支持:Java 8+、Scala 2.11+、Python 2.7/3.5+、Hadoop 2.6+以及Spark 2.1+。

5.可在Apache Spark上运行,在Apache Spark上,SystemML通过逐行查看代码,确保代码是否能够在Apache Spark聚类上运行。

未来对SystemML的开发将包括:使用GPU进行额外的深度学习,例如导入和运行神经网络架构以及用于训练的预训练模型。

SystemML的Java机器学习连接器(JMLC)

Java机器学习连接器(JMLC)API是一种编程接口,它在嵌入式时与SystemML进行交互。JMLC的主要目的是作为一个评分API,其中,评分函数是用SystemML的DML语言表示的。除了评分外,嵌入式SystemML还可在一台机器上运行的更大的应用程序的上下文中,执行聚类等无监督学习任务。

Apache Mahout

Apache Mahout是一个分布式线性代数框架,提供了一些经典的机器学习算法。

Mahout旨在帮助开发人员方便快捷的实现自己的算法。Apache Spark是一种即拿即用的分布式后台,或者也可以将其扩展到其他分布式后台。其特点如下:

1.数学表达Scala DSL。

2.支持多种分布式后端(包括Apache Spark)。

3.包含用于CPU / GPU / CUDA加速的模块化本地求解器。

4.Apache Mahout应用的领域包括:协作过滤(CF),聚类和分类。

功能

1.Taste CF.Taste 是 Sean Owen 在 SourceForge 上发起的一个针对协同过滤(CF)的开源项目,并在 2008 年被赠予 Mahout。

2.支持 Map-Reduce 的集群实现包括 :k-Means、模糊 k-Means、Canopy、Dirichlet 和 Mean-Shift算法等。

3.分布式朴素贝叶斯和互补朴素贝叶斯的分类实现。

4.用于进化编程的分布式适应度函数。

5.矩阵和矢量库。

OpenNN

OpenNN是一个用c++编写的开源类库,它实现了神经网络建模。

Opennn (开放神经网络图书馆)以前被称为Flood,它是以R. Lopez在2008年泰罗尼亚技术大学的博士论文《在工程变分问题的神经网络》为基础开发的。

Opennn使用一组函数实现了数据挖掘,并且,可以使用一个API将这些函数嵌入到其他软件工具中,使软件工具和预测分析任务之间进行交互。Opennn的主要优点就是它的高性能。由于采用c++开发,因此它有更好的内存管理和更高的处理速度,并利用 OpenMP 和 GPU 加速度(CUDA)实现 CPU 并行化。

Opennn包中含有单元测试、许多示例和大量文档。为神经网络算法和应用的研究开发提供了一个有效的框架。神经网络设计是一个基于OpenNN的专业预测分析工具,这就意味着神经网络设计的神经引擎是基于 OpenNN 建立的。

Neuroph

Neuroph是一种用 Java 编写的面向对象的神经网络框架。

Neuroph可用于在Java程序中创建和训练神经网络,它提供了Java类库以及用于创建和训练神经网络的GUI工具easyNeurons。Neuroph是一个轻量级的Java神经网络框架,可用于开发常见的神经网络架构。它包含一个设计良好的开源Java库,其中包含少量与基本神经网络概念对应的基础类。它还有一个很好的GUI神经网络编辑器来快速创建Java神经网络组件。目前,已经在Apache 2.0许可下作为开源发布出来。

Neuroph的核心类与人工神经元、神经元层、神经元连接、权重、传递函数、输入函数和学习规则等基本神经网络概念对应。Neuroph支持常见的神经网络体系结构,例如具有反向传播,Kohonen和Hopfield网络的多层感知器。所有的这些类都可以进行扩展和定制,以自定义创建神经网络和学习规则。Neuroph同时也支持图像识别。

Deeplearning4j

Deeplearning4j是第一个为Java和Scala编写的商业级开源分布式深度学习库。

Deeplearning4j旨在成为顶尖的即拿即用设备,而不是只是做一些配置,这使得非专业人员也能够快速的构建模型。

DL4J可以通过Keras(包括TensorFlow,Caffe和Theano)从大多数主要框架中导入神经网络模型,它为数据科学家、数据工程师和DevOps提供了跨团队工具包,弥合了Python生态系统和JVM之间的障碍。现在,Keras是Deeplearning4j的Python API。

功能

1.分布式 cpu 和 gpu

2.Java, Scala and Python APIs

3.适用于微服务体系结构

4.通过降低迭代次数进行并行训练

5.在Hadoop 上可伸缩

6.在AWS扩展上提供Gpu 支持

1.Deeplearning4J: 神经网络平台

2.ND4J: Numpy for the JVM

3.DataVec:机器学习ETL操作的工具

4.JavaCPP:Java和C ++之间的桥梁

5.Arbiter:机器学习算法的评估工具

6.RL4J:JVM的深度增强学习

Mycroft

Mycroft声称是世界上第一个开源助手,适用于从科学项目到企业软件应用程序的任何事情。

Mycroft可以在任何地方运行——台式计算机上、在汽车内或在树莓派上运行。这是可以自由混合、自由扩展和改进的开源软件。

OpenCog

OpenCog是一个旨在构建开源人工智能框架的项目。

OpenCog是认知算法的多元化组合,每种组合都体现了它们的创新之处。但是,认真遵守认知协同原则才是OpenCog整体架构强大的原因。OpenCog最初是基于2008年“Novamente Cognition Engine”(NCE)发布的源代码。

1.一个图表数据库,它包含术语、原子公式、句子和关系作为超图。

2.一个模理论解算器,作为通用图形查询引擎的一部分,用于执行图和超图模式匹配。

3.一种称为元优化语义进化搜索的概率遗传程序(MOSES),最初由在Google工作的Moshe Looks开发。

4.有一个基于 OpenPsi 和 Unity 的虚拟世界中的交互学习应用体系。

5.有一个由Link Grammar和RelEx组成的自然语言输入系统,它们都采用类AtomSpace来表示语义和句法的关系。

6.有一个称为SegSim的自然语言生成系统,它实现NLGen和NLGen2。

7.Psi 理论的实现,用于处理情绪状态、驱动和冲动,称为 OpenPsi。

**目前主流的是Tensorflow和Pytorch较多,国内的百度飞浆号称中国版TensorFlow也值得关注,百度* 飞浆* 提供了不少应用场景,有利于快速开发

使用logstash同步至ES的几个坑 - 一位帅气的网友的个人空间 - OSCHINA - 中文开源技术交流社区

$
0
0

1.前言

记录使用logstash从sqlserver同步数据到ES中遇到的几点问题。使用的版本是es6.8.3+logstash6.8.3

2.logstash配置文件

2.1input

input {
    jdbc {
        jdbc_driver_library => "/usr/local/logstash-6.8.3/logstashconfs/sqljdbc4.jar"#sqlserver的驱动jar包jdbc_driver_class => "com.microsoft.sqlserver.jdbc.SQLServerDriver"
        jdbc_connection_string => "jdbc:sqlserver://192.168.1.101:1433;databaseName=test;"
        jdbc_user => "sa"
        jdbc_password => "123456"
        jdbc_default_timezone => "Asia/Shanghai"
		jdbc_paging_enabled => "true"#分页record_last_run => true#记录上一次运行的值use_column_value => true#使用数据库中的字段追踪tracking_column => "update_time"#追踪的字段名称tracking_column_type => "timestamp"#追踪的字段类型last_run_metadata_path => "/usr/local/logstash-6.8.3/logstashconfs/sync-logs/consumer_statistics_update_time"#上一次运行的值存储的文件地址clean_run => false#使用数据库中的字段追踪statement => "SELECT * FROM v_test WHERE update_time>:sql_last_value and update_time<GETDATE() "#sql语句schedule => "*/5 * * * * *"#每5s执行一次}
}
  • statement

由于要查的数据是表关联的数据,一开始想的是建立多个jdbc,把数据存到es的不同的索引中,利用父子文档进行关联查询,

后来发现这种办法效率差,而且影响ES的性能,所以解决办法就是在sqlserver中建立好多表联查好的视图,

这里的 statement中的v_test就是创建好的视图.

由于设置了Logstash 增量更新, 必须要使用 update_time>:sql_last_value and update_time<GETDATE()这种限制条件,这样才可以保证数据不丢失也不重复

具体原因见: 如何使用 Logstash 实现关系型数据库与 ElasticSearch 之间的数据同步

  • schedule

网上的很多教程都说最小间隔是1min,实际上是可以做到秒级的.

schedule => "*/5 * * * * *"只要在前面再加一个* 单位就是秒,这里就是每5s执行一次

2.2filter

filter {
	if ![test]{ruby{code =>'event.set("test","")'}}	
	mutate{
		convert => { "id" => "integer" }
		remove_field => ["@timestamp"]
		remove_field => ["@version"]
	}
}

这里主要是对从sqlserver数据库查出来的数据进行一些处理,我这里删去了大多数的内容,仅保留一些代表性的.

  • if ![test]{ruby{code =>'event.set("test","")'}}

这个的意思是 test字段为null时,使用ruby这个语言进行处理, code =>''这里面就是写代码的

event.set("test","")意思就是 设置test字段的内容为""

当然我们也可以先 event.get("test"),获取test字段的内容,然后在进行一系列处理后,再 event.set,这样就可以保存处理后的字段的值

ruby语言的具体语法可以参考这个: Ruby教程

  • convert => { "id" => "integer" }

这个的意思就是将id字段的类型转化为integer,如果某个字段是时间类型可以转化为timestamp类型

2.3output

output {
		elasticsearch {
			hosts => ["htkj101:9200","htkj102:9200","htkj103:9200"]
			index => "consumer_statistics"#索引名称document_id => "%{id}"#索引的iddocument_type => "consumer_statistics"#索引的type,这个在6.x版本以后就已经被废弃,可以忽略这个template_name => "consumer_statistics"#索引模板的名称}
}
  • document_id => "%{id}"

文档的id就是导入数据的id,这样设置可以实现幂等性

  • template_name => "consumer_statistics"

索引模板的名称 consumer_statistics,ES会调用模板名称为 consumer_statistics创建索引.

当然前提是你得先创建好这个模板

3.索引模板的创建

  • 指令

curl -H "Content-Type: application/json" -XPUT http://htkj101:9200/_template/consumer_statistics -d '在这里输入你创建的模板'
  • 模板

{"template": "consumer_statistics","order": 2,"version": 60001,"index_patterns": ["consumer_statistics"],"settings": {"index": {"refresh_interval": "5s","max_result_window": "2147483647"#设置from+size的最大值}
	},
	"mappings": {"_default_": {"dynamic_templates": [{"message_field": {"path_match": "message","mapping": {"norms": false,"type": "text"
					},"match_mapping_type": "string"
				}
			}, {"string_fields": {"mapping": {"norms": false,"type": "text","fields": {"keyword": {"ignore_above": 1024,#设置不被索引的字段长度"type": "keyword"
							}
						}
					},"match_mapping_type": "string","match": "*"
				}
			}],"properties": {"@timestamp": {"type": "date"
				},"geoip": {"dynamic": true,"properties": {"ip": {"type": "ip"
						},"latitude": {"type": "half_float"
						},"location": {"type": "geo_point"
						},"longitude": {"type": "half_float"
						}
					}
				},"@version": {"type": "keyword"
				}
			}
		}
	},"aliases": {}
}
  • "max_result_window": "2147483647"

在业务处理的过程中往往需要分页,ES的JAVA-API是通过from,size来设置分页数量和每页的数量,

在默认的情况下from+size必须要小于10000,但是如果实际需求大于10000,则必须在这里设置

我这里设置的是 max_result_window的最大值,实际情况中不需要设置如此之大,

因为ES会在内存中进行排序,如果一次返回的结果过大,可能会导致服务宕机.

  • "ignore_above": 1024

这里默认是256,意思是如果某一个字段的内容超过256字节的话,那么将不会被索引.

也就是说从ES中是能够看到这条数据的存在,但是如果你指定查询条件,是查不出来的.

举个例子,现在ES中有id,test两个字段,一共100条数据

test字段中只有一条数据超过了256字节,现在我查询test字段中包含"1"的数据,

即使这个超过256字节的数据含有1,但是也不会被查询到.

为了能够让他被索引到,这里将256改成1024,即只有超过1024字节才会不被索引.

  • 完整命令
curl -H "Content-Type: application/json" -XPUT http://htkj101:9200/_template/consumer_statistics -d '
{"template":"consumer_statistics","order":2,"version":60001,"index_patterns":["consumer_statistics"],"settings":{"index":{"refresh_interval":"5s","max_result_window":"2147483647"}},"mappings":{"_default_":{"dynamic_templates":[{"message_field":{"path_match":"message","mapping":{"norms":false,"type":"text"},"match_mapping_type":"string"}},{"string_fields":{"mapping":{"norms":false,"type":"text","fields":{"keyword":{"ignore_above":1024,"type":"keyword"}}},"match_mapping_type":"string","match":"*"}}],"properties":{"@timestamp":{"type":"date"},"geoip":{"dynamic":true,"properties":{"ip":{"type":"ip"},"latitude":{"type":"half_float"},"location":{"type":"geo_point"},"longitude":{"type":"half_float"}}},"@version":{"type":"keyword"}}}},"aliases":{}}'

在创建模板的过程中,发现总是创建失败,后来发现弄成这样的两行,就不会出错了.

免费开源剪辑软件Shotcut推荐和使用教程 - 简书

$
0
0

最近想剪辑一下教学视频,想着能不用盗版尽量不用盗版,况且自己的需求并不复杂,又不是剪辑电影电视剧了,就没有下载那几个大牌的剪辑软件。

简单研究了一下免费的剪辑软件,最后选择了Shutcut。这是一个开源的免费软件,官网地址是 https://shotcut.org/。官网进去是英文版的,不要恐慌,软件下载下来界面有中文版。

Shotcut软件的界面.png

下面大概说一下这个软件怎么使用。

导入素材,建立时间线

  1. 将需要剪辑的素材拖动到「播放列表」区域,将素材导入到软件。

  2. 点击时间线下面的「三横线符号」,打开附加操作菜单,在里面选择添加视频轨道,根据需要可以再添加音频轨道或多个视频轨道。

Shotcut剪辑-添加视频轨道.png
  1. 将素材拖动到对应的轨道上。如果需要素材先后播放,就拖动到同一个轨道上,如果需要视频以画中画的形式叠加播放,就拖动到不同轨道上。

音频视频对齐

教学视频往往会同时录制多个视频,比如一个是电脑录屏,一个是教师出境。

剪辑时如果两个视频没有对齐的话,教师的口型就对不上声音,或者PPT翻页和声音不一致。

Shotcut这个软件的一个好处是每个视频轨道都可以显示音频波形,通过对比音频波形可以很方便的对齐视频。视频对齐后,将其中一条的音轨关掉,保留收音效果较好的那一条视频的音轨。

音频对齐.png

调整画中画位置

改变上层视频的位置和大小

导入两个视频时,视频都会居中布置,如果需要调整上层视频的位置的话,需要在 「时间线」里面选中上层视频,然后在 「滤镜」中选择 「位置与尺寸」,就可以调整上层视频的位置了。

调整画中画位置.png

实际上,不光上层视频可以调整位置,下层视频也可以调整位置,从而做出两个视频并列的效果。

上层视频位置和大小的变换

有时在教学视频里面,上层视频(比较教师出境视频)的大小和位置是需要变化的,比如说PPT首页的时候,教师出境视频比较大,内页的时候,出境视频会比较小。

这种情况,首先需要对视频进行切割。选中需要切割的视频,将播放点的竖线拖动到你要切割的位置。在时间线上面的工具栏选择 「于播放点处切割」,选中的视频就可以被切割为2个视频,可以单独设置滤镜,选择 「位置和尺寸」滤镜,就可以重新设置上层视频的位置和尺寸了。

这里有一个小技巧,通过音频波形,可以迅速的找到PPT的翻页点,因为翻页点上基本不说话。

切割视频.png

因为上层视频发生了变化,为了不显得太突兀,可以增加一个淡入淡出的效果。

在前一段视频上增加一个 视频淡出滤镜,在后一段视频上增加一个 视频淡入滤镜。完成后时间轴上会出现两个半透明的三角形。

淡入淡出.png

显示字幕、文字

首先在视频轨道上方再增加一个新轨道。

然后在工具栏的 「打开其他」里面选择 「文本」,在打开的对话框里输入你要显示的文本内容。

插入文本.png

输入文本后点OK,在预览框里会显示文本的内容,将其拖动到新增加的轨道上。注意拖动的时候不要点中间那个点,要按住文本框的其他位置拖动。否则的话不会把文本框拖动到时间线上,而是会把文本框在画面上移动位置。

插入文本效果.png

将文本拖动到轨道上之后,就可以在滤镜里设置文本的字体、颜色等。

拖动文本效果.png

设置转场动画

如果在要把两个视频相连的话,可能需要设置转场动画。

将一个轨道上的两个视频重叠,重叠的部分就会变成紫色的沙漏形状。右键点击这个沙漏,选择 「属性」,就可以设置转场动画了。

转场动画.png

视频输出

最后就是进行视频输出了,也就是将剪辑好的视频按照一定的格式保存到本地。

Shutcut没有按照素材来源自动选择参数的功能(还是说我没发现?),需要手动选择参数。

在工具栏上选择 「输出」功能(图标是一个光盘💿),打开输出界面。来源选择 时间线,硬件编码器推荐不要勾选。勾选了硬件编码器的话,如果你的显卡支持硬件编码,导出会比较快,但是视频质量低于不使用硬件编码。

输出.png

左边的列表是常见的视频编码和参数选择,如果对编码器比较懂的话,还可以点 高级自己设置。如果对视频编码不太懂的话,这里推荐几个选项。

  1. 内建里面的「H.264 High Profile」,这个导出以后是「.mp4」格式,兼容性较好。

  2. 内建里面的「HEVC Main Profile」,这个导出以后也是「.MP4」文件。HEVC也叫H.265,压缩率比H.264高一些,但是个别老一点的设备可能不兼容。

选择格式之后,点击 「输出文件」,选择保存位置。

确定后,在界面最右边的任务里面会显示进度,泡杯茶耐心等待吧。

漫长的进度条.png

本文同时发表在我的博客 「吕旭说」上。

浅析开源项目之Ceph - 知乎

$
0
0

前言

Ceph是一个极其复杂的统一分布式存储系统,运维操作门槛高、稳定性不错,性能差强人意,虽然各大厂都在自研分布式存储,但Ceph是不可或缺的参考对象。本文参考了Ceph源代码以及网上各路大神文章,如有侵权,联系删除。简要分析Ceph的架构、重要的模块以及基于Seastar的未来规划,使读者对Ceph有一个大致清晰的认识。

目录

  • 1 Ceph概述
  • 2 核心组件
  • 3 IO流程
  • 4 IO顺序性
  • 5 PG一致性协议
    • 5.1 StateMachine
    • 5.2 Failover Overview
    • 5.3 PG Peering
    • 5.4 Recovery/Backfill
  • 6 引擎概述
  • 7 FileStore
    • 7.1 架构设计
    • 7.2 对外接口
    • 7.3 日志类型
    • 7.4 幂等操作
  • 8 BlueStore
    • 8.1 架构设计
    • 8.2 BlockDevice
    • 8.3 磁盘分配器
    • 8.4 BlueFS
    • 8.5 对象IO
  • 9 未来规划

1 Ceph概述

Ceph是由学术界(Sage Weil博士论文)在2006年提出的一个开源的分布式存储系统的解决方案,最早致力于下一代高性能分布式文件存储,经过十多年的发展,还提供了块设备、对象存储S3的接口,成为了统一的分布式存储平台,进而成为开源社区存储领域的明星项目,得到了广泛的实际应用。

Ceph是一个可靠的、自治的、可扩展的分布式存储系统,它支持文件存储、块存储、对象存储三种不同类型的存储,满足存储的多样性需求。整体架构如下:

  • 接口层:提供客户端访问存储层的的各种接口,支持POSIX文件接口、块设备接口、对象S3接口,以及用户可以自定义自己的接口。
  • Librados:提供上层访问RADOS集群的各种库函数接口,libcephfs、librbd、librgw都是Librados的客户端。
  • RADOS:可靠的、自治的分布式对象存储,主要包含Monitor、OSD、MDS节点,提供了一个统一的底层分布式存储系统,支持逻辑存储池概念、副本存储和纠删码、自动恢复、自动rebalance、数据一致性校验、分级缓存、基于dmClock的QoS等核心功能。

2 核心组件

  • CephFS:Ceph File System,Ceph对外提供的文件系统服务,MDS来保存CephFS的元数据信息,数据写入Rados集群。
  • RBD:Rados Block Device,Ceph对外提供的块设备服务,Ceph里称为Image,元数据很少,保存在特定的Rados对象和扩展属性中,数据写入Rados集群。
  • RGW:Rados Gateway,Ceph对外提供的对象存储服务,支持S3、Swift协议,元数据保存在特定的Pool里面,数据写入Rados集群。
  • Monitor:保存了MONMap、OSDMap、CRUSHMap、MDSMap等各种Map等集群元数据信息。一个Ceph集群通常需要3个Mon节点,通过Paxos协议同步集群元数据。
  • OSD:Object Storage Device,负责处理客户端读写请求的守护进程。一个Ceph集群包含多个OSD节点,每块磁盘一个OSD进程,通过基于PGLog的一致性协议来同步数据。
  • MDS:Ceph Metadata Server,文件存储的元数据管理进程,CephFS依赖的元数据服务,对外提供POSIX文件接口,不是Rados集群必须的。
  • MGR:Ceph Manager,负责跟踪运行时指标以及集群的运行状态,减轻Mon负担,不是Rados集群必须的。
  • Message:网络模块,目前支持Epoll、DPDK(剥离了seastar的网络模块,不使用其share-nothing的框架)、RDMA,默认Epoll。
  • ObjectStore:存储引擎,目前支持FileStore、BlueStore、KVStore、MemStore,提供类POSIX接口、支持事务,默认BlueStore。
  • CRUSH:数据分布算法,秉承着无需查表,算算就好的理念,极大的减轻了元数据负担(但是感觉过于执着减少元数据了,参考意义并不是很大),但同时数据分布不均,不过已有 CRUSH优化Paper
  • SCRUB:一致性检查机制,提供scrub(只扫描元数据)、deep_scrub(元数据和数据都扫描)两种方式。
  • Pool:抽象的存储池,可以配置不同的故障域也即CRUSH规则,包含多个PG,目前类型支持副本池和纠删池。
  • PG:Placement Group,对象的集合,可以更好的分配和管理数据,同一个PG的读写是串行的,一个OSD上一般承载200个PG,目前类型支持副本PG和纠删PG。
  • PGLog:PG对应的多个OSD通过基于PGLog的一致性协议来同步数据,仅保存部分操作的oplog,扩缩容、宕机引起的数据迁移过程无需Mon干预,通过PG的Peering、Recovery、Backfill机制来自动处理。
  • Object:Ceph-Rados存储集群的基本单元,类似文件系统的文件,包含元数据和数据,支持条带化、稀疏写、随机读写等和文件系统文件差不多的功能,默认4MB。

3 IO流程

此处以RBD块设备为例简要介绍Ceph的IO流程。

  1. 用户创建一个Pool,并指定PG的数量。
  2. 创建Pool/Image,挂载RBD设备,映射成一块磁盘。
  3. 用户写磁盘,将转换为对librbd的调用。
  4. librbd对用户写入的数据进行切块并调用librados,每个块是一个object,默认4MB。
  5. librados进行 stable_hash算法计算object所属的PG,然后再输入pg_id和CRUSHMap,根据CRUSH算法计算出PG归属的OSD集合。
  6. librados将object异步发送到Primary PG,Primary PG将请求发送到Secondary PG。
  7. PG所属的OSD在接收到对应的IO请求之后,调用ObjectStore存储引擎层提供的接口进行IO。
  8. 最终所有副本都写入完成才返回成功。

Ceph的IO通常都是异步的,所以往往伴随着各种回调,以FileStore为例看下ObjectStore层面的回调:

  1. on_journal:数据写入到journal,通常通过DirectIO + Libaio的方式,Journal的数据是sync到磁盘上的。
  2. on_readable:数据写入Journal且写入Pagecache中,返回客户端可读。
  3. on_commit:Pagecache中的数据sync到磁盘上,返回客户端真正写成功。

4 IO顺序性

分布式系统中通常需要考虑对象读写的顺序性和并发性,如果两个对象没有共享资源,那么就可以并发访问,如果有共享资源就需要加锁操作。对于同一个对象的并发读写来说,通常是通过队列、锁、版本控制等机制来进行并发控制,以免数据错乱,Ceph中对象的并发读写也是通过队列和锁机制来保证的。

PG

Ceph引入PG逻辑概念来对对象进行分组,不同PG之间的对象是可以并发读写的,单个PG之间的对象不能并发读写,也即理论上PG越多并发的对象也越多,但对于系统的负载也高。

不同对象的并发控制

落在不同PG的不同对象是可以并发读写的,落在统一PG的不同对象,在OSD处理线程中会对PG加锁,放进PG队列里,一直等到调用queue_transactions把OSD的事务提交到ObjectStore层才释放PG的锁,也即

对于同一个PG里的不同对象,是通过PG锁来进行并发控制,不过这个过程中不会涉及到对象的IO,所以不太会影响效率。

同一对象的并发控制

同一对象的并发控制是通过PG锁实现的,但是在使用场景上要分为单客户端、多客户端。

  1. 单客户端:单客户端对同一个对象的更新操作是串行的,客户端发送更新请求的顺序和服务端收到请求的顺序是一致的。
  2. 多客户端:多客户端对同一个对象的并发访问类似于NFS的场景,RADOS以及RBD是不能保证的,CephFS理论上应该可以。

所以接下来主要讨论单客户端下同一对象的异步并发更新。

Message层顺序性

  1. TCP层是通过消息序列号来保证一条连接上消息的顺序性。
  2. Ceph Message层也是通过全局唯一的tid来保证消息的顺序性。

PG层顺序性

从Message层取到消息进行处理时,OSD处理OP时划分了多个shard,每个shard可以配置多个线程,PG通过哈希的方式映射到不同的shard里面。OSD在处理PG时,从拿到消息就会PG加了写锁,放入到PG的OpSequencer队列,等到把OP请求下发到ObjectStore端才释放写锁。对于同一个对象的并发读写通过对象锁来控制。

对同一个对象进行写操作会加write_lock,对同一个对象的读操作会加read_lock,也就是读写锁,读写是互斥的。写锁从queue_transactions开始到数据写入到Pagecache结束。

对同一个对象上的并发写操作,实际上并不会发生,因为放入PG队列是有序的,第一次写从PG取出放到ObjectStore层之后就会释放锁,然后再把第二次写从PG取出放入到ObjectStore层,取出写OP放到ObjectStore层都是调的异步写的接口,这就需要ObjectStore层来保证两次写的顺序性了。

ObjectStore层顺序性

ObjectStore支持FileStore、BlueStore,也都需要保证IO顺序性。对于写请求,到达ObjectStore层之后,会获取OpSequencer(每个PG一个,用来保证PG内OP顺序)。

FileStore:对于写事务OP来说(都有一个唯一递增的seq),会按照顺序放进writeq队列,然后write_thread线程通过Libaio将数据写入到Journal里面,此时数据已经是on_disk但不可读,已完成OP的seq序号按序放到journal的finisher队列里(因为Libaio并不保证顺序,会出现先提交的IO后完成,因此采用op的seq序号来保证完成后处理的顺序),如果某个op之前的op还未完成,那么这个op会等到它之前的op都完成后才一起放到finisher队列里,然后把数据写入到Pagecache和sync到数据盘上。

BlueStore:bluestore在拿到写OP时会先通过BlockDevice提供的异步写(Libaio/SPDK/io_uring)接口先把数据写到数据盘,然后再通过RocksDB的WriteBatch接口批量的写元数据和磁盘分配器信息到RocksDB。由于也是通过异步写接口写的,也需要等待该OP之前的OP都完成,才能写元数据到RocksDB。

5 PG一致性协议

在Ceph的设计和实现中,自动数据迁移、自动数据均衡等各种特性都是以PG为基础实现的,PG是最复杂和最难理解的概念,Ceph也基于PG实现了数据的多副本和纠删码存储。基于PG LOG的一致性协议也类似于Raft实现了强一致性。

5.1 StateMachine

PG有20多种状态,状态的多样性也反映了功能的多样性和复杂性。PG状态的变化通过事件驱动的状态机来驱动,比如集群状态的变化,OSD加入、删除、宕机、恢复 、创建Pool等,最终都会转换为一系列的状态机事件,从而驱动状态机在不同状态之间跳转和执行处理。

  • Active:活跃态,PG可以正常处理来自客户端的读写请求,PG正常的状态应该是Active+Clean的。
  • Unactive:非活跃态,PG不能处理读写请求。
  • Clean:干净态,PG当前不存在修复对象,Acting Set和Up Set内容一致,并且大小等于存储池的副本数。
  • Peering:类似Raft的Leader选举,使一个PG内的OSD达成一致,不涉及数据迁移等操作。
  • Recovering:正在恢复态,集群正在执行迁移或恢复某些对象的副本。
  • Backfilling:正在后台填充态,backfill是recovery的一种特殊场景,指peering完成后,如果基于当前权威日志无法对Peers内的OSD实施增量同步(OSD离线太久,新的OSD加入) ,则通过完全拷贝当前Primary所有对象的方式进行全量同步。
  • Degraded:降级状态,Peering完成后,PG检测到有OSD有需要被同步或修复的对象,或者当前ActingSet 小于存储池副本数。
  • Undersized:PG当前Acting Set小于存储池副本数。ceph默认3副本,min_size参数通常为2,即副本数>=2时就可以进行IO,否则阻塞IO。
  • Scrubing:PG正在进行对象的一致性扫描。
  • 只有Active状态的PG才能进行IO,可能会有active+clean(最佳)、active+unclean(小毛病)、active+degraded(小毛病)等状态,小毛病不影响IO。
为了避免全是文字,网上找了张图,如有侵权,联系删除。

5.2 Failover Overview

故障检测:Ceph分为MON集群和OSD集群两部分,MON集群管理者整个集群的成员状态,将OSD的信息存放在OSDMap中,OSD定期向MON和Peer OSD 发送心跳包,声明自己处于在线状态。MON接收来自OSD的心跳信息确认OSD在线,同时也接收来自OSD对于Peer OSD的故障检测。当MON判断某个OSD节点离线后,便将最新的OSDMap通过心跳随机的发送给OSD,当Client或者OSD处理IO请求时发现自身的OSDMap版本低于对方,便会向MON请求最新的OSDMap,这种Lasy的更新方式,经过一段时间的传播之后,整个集群都会收到最新的OSDMap。

确定恢复数据:OSD在收到OSDMap的更新消息后,会扫描该OSD下所有的PG,如果发现某些PG已经不属于自己,则会删掉其数据。如果该OSD上的PG是Primary PG的话,将会进行PG Peering操作。在Peering过程中,会根据PGLog检查多个副本的一致性,并计算PG的不同副本的数据缺失情况,PG对应的副本OSD都会得到一份对象缺失列表,然后进行后续的Recovery,如果是新节点加入、不足以根据PGLog来Recovery等情况,则会进行Backfill,来恢复整份数据。

数据恢复:在PG Peering过程中会暂停所有的IO,等Peering完成后,PG会进入Active状态,此时便可以接收数据的IO请求,然后根据Peering的信息来决定进行Recovery还是Backfill。对于Replica PG缺失的数据Primary PG会通过Push来推送,对于Primary PG自身缺少的数据会通过Pull方式从其他Replicate PG拉取。在Recovery过程中,恢复的粒度是4M对象,对于无法通过PGlog来恢复的,则进行Backfill进行数据的全量拷贝,等到数据恢复完成后,PG的状态会标记为Clean即所有副本数据保持一致。

5.3 PG Peering

PG的Peering是使一个PG内的所有OSD达成一致的过程,相关重要概念如下:

  • up set:pg对应的副本列表,也即通过CRUSH算法选出来的3个副本列表,第一个为primary,其他的为replica。
  • active set:对外处理IO的副本列表,通常和up set一致,当恢复时可能会存在临时PG,则active set为临时PG的副本集合,用于对外提供正常IO,当完成恢复后,active set调整为up set。
  • pg_temp:临时的PG,当CRUSH算法产生新的up set的primary无法承担起职责(新加入的OSD或者PGLog过于落后的OSD成为了primary,也即需要backfill的primary需要申请临时PG,recovery的primary不需要申请临时PG),osd就会向mon申请一个临时的PG用于数据正常IO和恢复,Ceph做了优化是在进行CRUSH时就根据集群信息选择是否预填充pg_tmp,从而减少Peering的时间。此时处于Remapped状态,等到数据同步完成,需要取消pg_tmp,再次通过Peering将active_set切回up_set。
  • epoch:每个OSDMap都会有一个递增的版本,值越大版本越新,当集群中OSD发生变化时,就会产生新的OSDMap。
  • pg log:保存操作的记录,是用于数据恢复的重要结构。并不会保存所有的op log,默认3000条,当有数据需要恢复的时候就会保存10000条。
  • Interval:每个PG都有Interval(epoch的操作序列),每次OSD获取到新的OSDMap时,如果发现 up set、up primary、active set、active primary没有改变,则Interval不用改变,否则就要生成新的current interval,之前的变成past_interval,只要该PG内部的OSD不发生变化,Interval就不会变化。

主要包含三个步骤:

  1. GetInfo:作用为确定参与peering过程的osd集合。主OSD会获取该PG对应的所有OSD的pg_info信息放入peer_info。
  2. GetLog:作用为选取权威日志。根据各个副本OSD的pg_info信息比较,选取一个具有权威日志的OSD,如果主OSD不具备权威日志,那么就从该具有权威日志的OSD拉取权威日志,拉取完成之后进行合并就具有了权威日志,如果primary自身具有权威日志,则不用合并,否则合并的过程如下:
    1. 拉取过来的日志比primary具有更老的日志条目:追加到primary本地日志尾部即可。
    2. 拉取过来的日志比primary具有更新的日志条目:追加到primary本地日志头部即可。
    3. 合并的过程中,primary如果发现自己有对象需要修复,便会将其加入到missing列表。
  3. GetMissing:获取需要恢复的object集合。主OSD拉取其他从OSD的PGLog,与自身权威日志进行对比,计算该OSD缺失的object集合。

5.4 Recovery/Backfill

Peering进行之后,如果Primary检测到自身或者任意一个Peer需要修复对象,则进入Recovery状态,为了影响外部IO,也会限制恢复的速度以及每个OSD上能够同时恢复的PG数量。Recovery一共有两种状态:

  1. Pull:如果Primary自身存在待恢复对象,则按照missing列表寻找合适的副本拉取修复对象到本地然后修复。
  2. Push:如果Primary检测到其Replica存在待恢复对象,则主动推动待修复对象到Replica,然后由Replica自身修复。

通常总是先执行Pull再执行Push,即先修复Primary再修复Replica,因为Primary承担了客户端的读写,需要优先进行修复,修复情况大致如下:

  1. 客户端IO和内部恢复IO可以同时进行。
  2. 读写的对象不在恢复列表中:按照正常IO即可。
  3. 读取的对象在恢复列表中:如果primary有则可以直接读取,如果没有需要优先恢复该对象,然后读取。
  4. 写入的对象在恢复列表中:优先恢复该对象,然后写入。
  5. backfill则是primary遍历当前所有的对象,将他们全量拷贝到backfill 的PG中。
  6. 恢复完成后,会重新进行Peering,是active set 和up set保持一致,变为active + clean状态。

在恢复对象时,由于PGLog并未记录关于对象修改的详细信息(offset、length等),所以目前对象的修复都是全量对象(4M)拷贝,不过社区已经支持 部分对象修复

同时在恢复对象时,由于ObjectStore支持覆盖写,所以在对象上新的写不能丢弃老的对象,需要等老的对象恢复完之后,才能进行该对象新的写入,不过社区已经支持 异步恢复

6 引擎概述

Ceph提供存储功能的核心组件是RADOS集群,最终都是以对象存储的形式对外提供服务。但在底层的内部实现中,Ceph的后端存储引擎在近十年来经历了许多变化。现如今的Ceph系统中仍然提供的后端存储引擎有FileStore、BlueStore。但该三种存储引擎都是近年来才提出并设计实现的。Ceph的存储引擎也先后经历了EBOFS-->FileStore/btrfs-->FileStore/xfs-->NewStore-->BlueStore。同时Ceph需要支持文件存储,所以其存储引擎提供的接口是类POSIX的,存储引擎操作的对象也具有类似文件系统的语义,也具有其自己的元数据。

7 FileStore

FileStore是Ceph基于文件系统的最早在生成环境比较稳定的单机存储引擎,虽然后来出现了BlueStore,但在一些场景中仍然不能代替FileStore,比如在全是HDD的场景中FileStore可以使用NVME盘做元数据和数据的读写Cache,从而加速IO,BlueStore就只能加速元数据IO。

7.1 架构设计

FileStore是基于文件系统的,为了维护数据的一致性,写入之前数据会先写Journal,然后再写到文件系统,会有一倍的写放大。不过Journal也起到了随机写转换为顺序写、支持事务的作用。

引用网上图片,如有侵权,联系删除。

7.2 对外接口

对象的元数据使用KV形式保存,主要有两种保存方式:

  • xattrs:保存在本地文件系统的扩展属性中,一般都有大小的限制。
  • omap:object map,保存在LevelDB/RocksDB中。

有些文件系统不支持扩展属性,或者扩展属性大小有限制。一般情况下xattr保存一些比较小且经常访问的元数据,omap保存一些大的不经常访问的元数据。

同时ObjectStore使用Transaction类来实现相关的操作,将元数据和数据封装到bufferlist里面,然后写Journal。大致包含OP_TOUCH、OP_WRITE、OP_ZERO、OP_CLONE等42种 事务操作。提供的对外接口大致有:

ObjectStore本身的接口:mount、umount、fsck、repair、mkfs等。

Object本身的接口:read、write、omap、xattrs、snapshot等。

7.3 日志类型

在FileStore的实现中,根据不同的日志提交方式,有两种不同的日志类型:

  • Journal writeahead:先提交数据到Journal上(通常配置成一块SSD磁盘),然后再写入到Pagecache,最后sync到数据盘上。适用于XFS、EXT4等不支持快照的文件系统,是FileStore默认的实现方式。
  • Journal parallel:数据提交到Journal和sync到数据盘并行进行,没有完成的先后顺序,适用于BTRFS、ZFS等支持快照的文件系统,由于文件系统支持快照,当写数据盘出错,数据不一致时,文件系统只需要回滚到上一次快照,并replay从上次快照开始的日志就可以,性能要比writeahead高,但是Linux下BTRFS和ZFS不稳定,线上生产环境几乎没人用。

日志处理有三个阶段:

  1. 日志提交(journal submit):数据写入到日志盘,通常使用DirectIO+Libaio,一个单独的write_thread不断从队列取任务执行。
  2. 日志应用(journal apply):日志对应的修改更新到文件系统的文件上,此过程仅仅是写入到了Pagecache。
  3. 日志同步(journal commit):将文件系统的Pagecache脏页sync到磁盘上,此时数据已经持久化到数据盘,Journal便可以删除对应的数据,释放空间。

7.4 幂等操作

在机器异常宕机的情况下,Journal中的数据不一定全部都sync到了数据盘上,有可能一部分还在Pagecache,此时便需要在OSD重启时保证数据的一致性,对Journal做replay。FileStore将已经sync到数据盘的序列号记录在commit_op_seq中,replay的时候从commit_op_seq开始即可。

但是在replay的时候,部分op可能已经sync到数据盘中,但是commit_op_seq却没有体现,序列化比其小,此时如果仍然replay,可能会出现非幂等操作,导致数据不一致。

假设一个事务包含如下3个操作:

  1. clone a 到 b。
  2. 更新 a。
  3. 更新 c。

假设上述操作都做完也已经持久化到数据盘上了,然后立马进程或者系统崩溃,此时sync线程还未来得及更新commit_op_seq,重启回放时,第二次执行clone操作就会clone到a新的数据版本,就会发生不一致。

FileStore在对象的属性中记录最后操作的三元组(序列号、事务编号、OP编号),因为journal提交的时候有一个唯一的序列号,通过这个序列号, 就可以找到提交时候的事务,然后根据事务编号和OP编号最终定位出最后操作的OP。对于非幂等的操作,操作前先检查下,如果可以继续执行就执行操作,执行完之后设置一个guard。这样对于非幂等操作,如果上次执行过, 肯定是有记录的,再一次执行的时候check就会失败,就不继续执行。

8 BlueStore

Ceph早期的单机对象存储引擎是FileStore,为了维护数据的一致性,写入之前数据会先写Journal,然后再写到文件系统,会有一倍的写放大,而同时现在的文件系统一般都是日志型文件系统(ext系列、xfs),文件系统本身为了数据的一致性,也会写Journal,此时便相当于维护了两份Journal;另外FileStore是针对HDD的,并没有对SSD作优化,随着SSD的普及,针对SSD优化的单机对象存储也被提上了日程,BlueStore便由此应运而出。

BlueStore最早在Jewel版本中引入,用于在SSD上替代传统的FileStore。作为新一代的高性能对象存储后端,BlueStore在设计中便充分考虑了对SSD以及NVME的适配。针对FileStore的缺陷,BlueStore选择绕过文件系统,直接接管裸设备,直接进行对象数据IO操作,同时元数据存放在RocksDB,大大缩短了整个对象存储的IO路径。BlueStore可以理解为一个支持ACID事物型的本地日志文件系统。

8.1 架构设计

BlueStore是一个事务型的本地日志文件系统。因为面向下一代全闪存阵列的设计,所以BlueStore在保证数据可靠性和一致性的前提下,需要尽可能的减小日志系统中双写带来的影响。全闪存阵列的存储介质的主要开销不再是磁盘寻址时间,而是数据传输时间。因此当一次写入的数据量超过一定规模后,写入Journal盘(SSD)的延时和直接写入数据盘(SSD)的延迟不再有明显优势,所以Journal的存在性便大大减弱了。但是要保证OverWrite(覆盖写)的数据一致性,又不得不借助于Journal,所以针对Journal设计的考量便变得尤为重要了。

一个可行的方式是使用增量日志。针对大范围的覆盖写,只在其前后非磁盘块大小对齐的部分使用Journal,即RMW,其他部分直接重定向写COW即可。

RWM(Read-Modify-Write):指当覆盖写发生时,如果本次改写的内容不足一个BlockSize,那么需要先将对应的块读上来,然后再内存中将原内容和待修改内容合并Merge,最后将新的块写到原来的位置。但是RMW也带来了两个问题: 一是需要额外的读开销; 二是如果磁盘中途掉电,会有数据损坏的风险。为此我们需要引入Journal,先将待更新数据写入Journal,然后再更新数据,最后再删除Journal对应的空间。

COW(Copy-On-Write):指当覆盖写发生时,不是更新磁盘对应位置已有的内容,而是新分配一块空间,写入本次更新的内容,然后更新对应的地址指针,最后释放原有数据对应的磁盘空间。理论上COW可以解决RMW的两个问题,但是也带来了其他的问题: 一是COW机制破坏了数据在磁盘分布的物理连续性。经过多次COW后,读数据的顺序读将会便会随机读。 二是针对小于块大小的覆盖写采用COW会得不偿失。 是因为一是将新的内容写入新的块后,原有的块仍然保留部分有效内容,不能释放无效空间,而且再次读的时候需要将两个块读出来做Merge操作,才能返回最终需要的数据,将大大影响读性能。 二是存储系统一般元数据越多,功能越丰富,元数据越少,功能越简单。而且任何操作必然涉及元数据,所以元数据是系统中的热点数据。COW涉及空间重分配和地址重定向,将会引入更多的元数据,进而导致系统元数据无法全部缓存在内存里面,性能会大打折扣。

基于以上设计理念,BlueStore的写策略综合运用了COW和RMW策略。 非覆盖写直接分配空间写入即可; 块大小对齐的覆盖写采用COW策略; 小于块大小的覆盖写采用RMW策略。整体架构设计如下图:

  • BlockDevice:物理块设备,使用Libaio、SPDK、io_uring操作裸设备,AsyncIO。
  • RocksDB:存储对象元数据、对象扩展属性Omap、磁盘分配器元数据。
  • BlueRocksEnv:抛弃了传统文件系统,封装RocksDB文件操作的接口。
  • BlueFS:小型的Append文件系统,实现了RocksDB::Env接口,给RocksDB用。
  • Allocator:磁盘分配器,负责高效的分配磁盘空间。
  • Cache:实现了元数据和数据的缓存。

8.2 BlockDevice

Ceph新的存储引擎BlueStore已成为默认的存储引擎,抛弃了对传统文件系统的依赖,直接管理裸设备,通过Libaio的方式进行读写。抽象出了 BlockDevice基类,提供统一的操作接口,后端对应不同的设备类型的实现(Kernel、NVME、PMEM)。

  • KernelDevice:通常使用Libaio或者io_uring,适用于HDD和SATA SSD。
  • NVMEDevice:通常使用SPDK用户态IO,提升IOPS缩短延迟,适用于NVME磁盘。
  • PMEMDevice:当做磁盘来用,使用libpmem库来操作。

IO架构图如下所示:

8.3 磁盘分配器

BlueStore直接管理裸设备,那么必然面临着如何高效分配磁盘中的块。BlueStore支持基于Extent和基于BitMap的两种磁盘分配策略,有 BitMap分配器(基于Bitmap)Stupid分配器(基于Extent),原则上都是尽量顺序分配而达到顺序写。

刚开始使用的是BitMap分配器,由于性能问题又切换到了Stupid分配器。之后Igor Fedotov大神重新设计和实现了 新版本BitMap分配器,性能也比Stupid要好,默认的磁盘分配器又改回了BitMap。

新版本BitMap分配器以Tree-Like的方式组织数据结构,整体分为L0、L1、L2三层。每一层都包含了完整的磁盘空间映射,只不过是slot以及children的粒度不同,这样可以加快查找,如下图所示:

新版本Bitmap分配器分配空间的大体策略如下:

  1. 循环从L2中找到可以分配空间的slot以及children位置。
  2. 在L2的slot以及children位置的基础上循环找到L1中可以分配空间的slot以及children位置。
  3. 在L1的slot以及children位置的基础上循环找到L0中可以分配空间的slot以及children位置。
  4. 在1-3步骤中保存分配空间的结果以及设置每层对应位置分配的标志位。

新版本Bitmap分配器整体架构设计有以下几点优势:

  1. Allocator避免在内存中使用指针和树形结构,使用vector连续的内存空间。
  2. Allocator充分利用64位机器CPU缓存的特性,最大程序的提高性能。
  3. Allocator操作的单元是64 bit,而不是在单个bit上操作。
  4. Allocator使用3级树状结构,可以更快的查找空闲空间。
  5. Allocator在初始化时L0、L1、L2三级BitMap就占用了固定的内存大小。
  6. Allocator可以支持并发的分配空闲,锁定L2的children(bit)即可,暂未实现。

BlueStore直接管理裸设备,需要自行管理空间的分配和释放。Stupid和Bitmap分配器的结果是保存在内存中的,分配结果的持久化是通过FreelistManager来做的。

FreelistManager最开始有extent和bitmap两种实现,现在默认为bitmap实现,extent的实现已经废弃。空闲空间持久化到磁盘也是通过RocksDB的Batch写入的。FreelistManager将block按一定数量组成段,每个段对应一个k/v键值对,key为第一个block在磁盘物理地址空间的offset,value为段内每个block的状态,即由0/1组成的位图,1为空闲,0为使用,这样可以通过与1进行异或运算,将分配和回收空间两种操作统一起来。

8.4 BlueFS

RocksDB不支持对裸设备的直接操作,文件的读写必须实现rocksdb::EnvWrapper接口,RocksDB默认实现有POSIX文件系统的读写接口。而POSIX文件系统作为通用的文件系统,其很多功能对于RocksDB来说并不是必须的, 同时RocksDB文件结构层次比较简单,不需要复杂的目录树,对文件系统的使用也比较简单,只使用追加写以及顺序读随机读。为了进一步提升RocksDB的性能,需要对文件系统的功能进行裁剪,而更彻底的办法就是考虑RocksDB的场景量身定制一套本地文件系统,BlueFS也就应运而生。相对于POSIX文件系统有以下几个优点:

  1. 元数据结构简单,使用两个map(dir_map、file_map)即可管理文件的所有元数据。
  2. 由于RocksDB只需要追加写,所以每次分配物理空间时进行提前预分配,一方面减少空间分配的次数,另一方面做到较好的空间连续性。
  3. 由于RocksDB的文件数量较少,可以将文件的元数据全部加载到内存,从而提高读取性能。
  4. 多设备支持,BlueFS将存储空间划分了3个层次:Slow慢速空间(存放BlueStore数据)、DB高速空间(存放sstable)、WAL超高速空间(存放WAL、自身Journal),空间不足或空间不存在时可自动降级到下一层空间。
  5. 新型硬件支持,抽象出了block_device,可以支持Libaio、io_uring、SPDK、PMEM、NVME-ZNS。

接口功能

RocksDB是通过BlueRocksEnv来使用BlueFS的,BlueRocksEnv实现了文件读写和目录操作,其他的都继承自rocksdb::EnvWrapper。

  • 文件操作:追加写、顺序读(适用于WAL的读,也会进行预读)、随机读(sstable的读,不会进行预读)、重命名、sync、文件锁。
  • 目录操作:目录的创建、删除、遍历,目录只有一级,即 /a 、 /a/b、/a/b/c 为同一级目录,整体元数据map可表示为:map<string(目录名), map<string(文件名), file_info(文件元数据)>>。

磁盘布局

BlueFS的数据结构比较简单,主要包含三部分,superblock、journal、data。

  • superblock:主要存放BlueFS的全局信息以及日志的信息,其位置固定在BlueFS的头部4K。
  • journal:存放元数据操作的日志记录,一般会预分配一块连续区域,写满以后从剩余空间再进行分配,在程序启动加载的时候逐条回放journal记录,从而将元数据加载到内存。也会对journal进行压缩,防止空间浪费、重放时间长。压缩时会遍历元数据,将元数据重新写到新的日志文件中,最后替换日志文件。
  • data:实际的文件数据存放区域,每次写入时从剩余空间分配一块区域,存放的是一个个sstable文件的数据。

元数据

BlueFS元数据:主要包含:superblock、dir_map、file_map、文件到物理地址的映射关系。

文件数据:每个文件的数据在物理空间上的地址由若干个extents表:一个extent包含bdev、offset和length三个元素,bdev为设备标识,因为BlueFS将存储空间设备划分为三层:慢速(Slow)空间、高速(DB)空间、超高速(WAL),bdev即标识此extent在哪块设备上,offset表示此extent的数据在设备上的物理偏移地址,length表示该块数据的长度。

structbluefs_extent_t{uint64_toffset=0;uint32_tlength=0;uint8_tbdev;}// 一个sstable就是一个fnodestructbluefs_fnode_t{uint64_tino;uint64_tsize;utime_tmtime;uint8_tprefer_bdev;mempool::bluefs::vector<bluefs_extent_t>extents;uint64_tallocated;}

按照9T盘、sstable 8MB,文件元数据80B来算,所需内存 9 * 1024 * 1024 / 8 * 80 / 1024 / 1024 = 90MB,说明把元数据全部缓存到内存并不会占用过多的内存。

加载流程

  1. 加载superblock到内存。
  2. 初始化各存储空间的块分配器。
  3. 日志回放建立dir_map、file_map来重建整体元数据。
  4. 标记已分配空间:BlueFS没有像BlueStore那样使用FreelistManager来持久化分配结果,因为sstable大小固定从不修改,所以BlueFS磁盘分配需求都是比较同意和固定的。会遍历每个文件的分配信息,然后移除相应的磁盘分配器中的空闲空间,防止已分配空间的重复分配。

读写数据

读数据:先从dir_map和file_map找到文件的fnode(包含物理的extent),然后从对应设备的物理地址读取即可。

写数据:BlueFS只提供append操作,所有文件都是追加写入。RocksDB调用完append以后,数据并未真正落盘,而是先缓存在内存当中,只有调用sync接口时才会真正落盘。

  1. open file for write
    打开文件句柄,如果文件不存在则创建新的文件,如果文件存在则会更新文件fnode中的mtime,在事务log_t中添加更新操作,此时事务记录还不会持久化到journal中。
  2. append file
    将数据追加到文件当中,此时数据缓存在内存当中,并未落盘,也未分配新的空间。
  3. flush data(写数据)
    判断文件已分配剩余空间(fnode中的 allocated - size)是否足够写入缓存数据,若不够则为文件分配新的空间;如果有新分配空间,将文件标记为dirty加到dirty_files当中,将数据进行磁盘块大小对其后落盘,此时数据已经写到硬盘当中,元数据还未更新,同时BlueFS中的文件都是追加写入,不存在原地覆盖写,就算失败也不会污染原来的数据。
  4. flush_and_sync_log(写元数据)
    从dirty_files中取到dirty的文件,在事务log_t中添加更新操作(即添加OP_FILE_UPDATE类型的记录),将log_t中的内容sync到journal中,然后移除dirty_files中已更新的文件。

第3步是写数据、第4步是写元数据,都涉及到sync落盘,整体一个文件的写入需要两次sync,已经算是很不错了。

8.5 对象IO

BlueStore中的对象非常类似于文件系统中的文件,每个对象在BlueStore中拥有唯一的ID、大小、从0开始逻辑编址、支持扩展属性等,因此对象的组织形式,类似于文件也是基于Extent。

BlueStore的每个对象对应一个Onode结构体,每个Onode包含一张extent-map,extent-map包含多个extent(lextent即逻辑的extent),每个extent负责管理对象内的一个逻辑段数据并且关联一个Blob,Blob包含多个pextent(物理的extent,对应磁盘上的一段连续地址空间的数据),最终将对象的数据映射到磁盘上。具体可参考BlueStore源码分析之对象IOBlueStore源码分析之事物状态机

BlueStore中磁盘的最小分配单元是min_alloc_size,HDD默认64K,SSD默认16K,里面有2种磁盘分配的写类型(分配磁盘空间,数据还在内存):

  1. big-write:对齐到min_alloc_size的写我们称为大写(big-write),在处理是会根据实际大小生成lextent、blob,lextent包含的区域是min_alloc_size的整数倍,如果lextent是之前写过的,那么会将之前lextent对应的空间记录下来并回收。
  2. small-write:落在min_alloc_size区间内的写我们称为小写(small-write)。因为最小分配单元min_alloc_size,HDD默认64K,SSD默认16K,所以如果是一个4KB的IO那么只会占用到blob的一部分,剩余的空间还可以存放其他的数据。所以小写会先根据offset查找有没有可复用的blob,如果没有则生成新的blob。

真正写磁盘时,有两种不同的写类型:

1、simple-write:包含对齐覆盖写(COW)和非覆盖写,先把数据写入新的磁盘block,然后更新RocksDB里面的KV元数据,状态转换图如下:

这图话的比较好,拿过来直接用了,如有侵权,联系删除。

2、deferred-write:为非对齐覆盖写,先把数据作为WAL写RocksDB即先写日志,然后会进行RMW操作写数据到磁盘,最后CleanupRocksDB中的deferred-write的数据。

这图话的比较好,拿过来直接用了,如有侵权,联系删除。

3、simple-write + deferred-write:上层的一次IO很有可能同时涉及到simple-write和deferred-write,其状态机就是上面两个加起来,只不过少了deferred-write的写WAL一步,因为可以在simple-write写元数据时就一同把WAL写入RocksDB。

9 未来规划

随着硬件的不断发展,IO的速度越来越快,PMEM和NVME也逐渐成为了存储系统的主流选择,相比之下CPU的速度没有那么快了,反而甚至成为了系统的瓶颈。如何高效合理的利用新型硬件是分布式存储不得不面临的一个重大问题。Ceph传统的线程模型是多线程+队列的模型,一个IO从发起到完成要经历重重队列和不同的线程池,锁竞争、上下文切换和Cache Miss比较严重,也导致IO延迟迟迟降不下来。通过Perf发现CPU主要都耗在了锁竞争和系统调用上,Ceph自身的序列化和反序列化也比较消耗CPU,所以需要一套新的编程框架来解决上述问题。Seastar是一套基于future-promsie现代化高效的share-nothing的网络编程框架,从18年开始,Ceph社区便基于Seastar来重构整个OSD,项目代号 Crimson,来更好的解决上述问题。

Crimson设计目标

  1. 最小化CPU开销。
  2. 减少跨核通信。
  3. 减少数据拷贝。
  4. Bypass Kernel,减少上下文切换。
  5. 支持新硬件:ZNS-NVME、PMEM等。

线程模型

性能对比

测试RBD时,在达到同等iops和延迟时,crimson-osd的cpu比ceph-osd的cpu少了好几倍。

BlueStore适配

BlueStore目前是Ceph里性能比较高的单机存储引擎,从设计研发到稳定差不多持续了3年时间,足以说明研发一个单机存储引擎的时间成本是比较高的。由于BlueStore不符合Seastar的编程模型,所以需要对BlueStore适配,目前有两种方案:

  1. BlueStore-Alien:使用一个Alien Thread,使用Seastar的编程模型专门向Seastar-Reactor提交BlueStore的任务。
  2. BlueStore-Native:使用Seastar-Env来实现RocksDB的Rocksdb-Env,从而更原生的适配。

但是由于RocksDB有自己的线程模型,外部不可控,所以无论怎么适配都不是最好的方案,理论上从0开始用基于Seastar的模型来写一个单机存储引擎是最完美的方案,于是便有了SeaStore,而BlueStore的适配也作为中间过渡方案,最多可用于HDD。

SeaStore

SeaStore是下一代的ObjectStore,适用于Crimson的后端存储,专门为了NVME设计,使用SPDK访问,同时由于Flash设备的特性,重写时必须先要进行擦除操作,也就是内部需要做GC,是不可控的,所以Ceph希望把Flash的GC提到SeaStore中来做:

  1. SeaStore的逻辑段(segment)理想情况下与硬件segment(Flash擦除单位)对齐。
  2. SeaStar是每个线程一个CPU核,所以将底层按照CPU核进行分段,每个核分配指定个数的segment。
  3. 当磁盘利用率达到阈值时,将少量的GC清理工作和正常的写流量一起做。
  4. 元数据使用B+数存储,而不是原来的RocksDB。
  5. 所有segment都是追加顺序写入的。

10 参考资源

  1. https://github.com/ceph/ceph-v13.1.0
  2. https://zhuanlan.zhihu.com/分步试存储
  3. Ceph设计原理与实现-中兴通讯
  4. Ceph十年经验总结:文件系统是否适合做分布式文件系统的后端
  5. Sage: bluestore-a-new-storage-backend-for-ceph
  6. Crimson: a-new-ceph-osd-for-the-age-of-persistent-memory-and-fast-nvme-storage

不止Docker:8款容器管理开源方案

$
0
0

Docker诞生于2013年,并普及了容器的概念,以至于大多数人仍然将容器的概念等同于“Docker容器”。

作为第一个吃螃蟹的人,Docker设置了新加入者必须遵守的标准。例如,Docker有一个大型系统镜像库。所有的替代方案都必须使用相同的镜像格式,同时试图改变Docker所基于的整个堆栈的一个或多个部分。

在此期间,出现了新的容器标准,容器生态系统朝着不同方向发展。现在除了Docker之外,还有很多方法可以使用容器。

在本文中,我们将介绍以下内容:
  • 将Chroot、cgroups和命名空间作为容器的技术基础
  • 定义Docker所基于的软件堆栈
  • 说明Docker和Kubernetes需要坚持和遵守的标准
  • 介绍替代解决方案,这些解决方案尝试使用具有更好更安全的组件来替换原始Docker容器。


容器的软件堆栈

像Chroot 调用、 cgroups 和命名空间等 Linux 特性帮助容器在与所有其他进程隔离的情况下运行,从而保证运行时的安全性。

Chroot

所有类似Docker的技术都起源于类似Unix操作系统(OS)的根目录。在根目录上方是根文件系统和其他目录。

从长远来看,这是很危险的,因为根目录中任何不需要的删除都会影响整个操作系统。这就是为什么存在一个系统调用 chroot()。它创建了额外的根目录,例如一个用于运行遗留软件,另一个用于包含数据库等等。

对于所有这些环境, chroot似乎是一个真正的根目录,而是实际上,它只是将路径名添加到任何以/开头的名字上。真正的根目录仍然存在,并且任何进程都可以引用指定根目录以外的任何位置。

Linux cgroups

自2008年2.6.24版本以来,Control groups(cgroups)一直是Linux内核的一项功能。Cgroup将同时限制、隔离和测量多个进程的系统资源(内存、CPU、网络和I/O)使用情况。

假设我们想阻止用户从服务器发送大量电子邮件。我们创建了一个内存限制为1GB、CPU占用率为50%的cgroup,并将应用程序的 processid添加到该组中。当达到这些限制时,系统将限制电子邮件发送过程。它甚至可能终止进程,这取决于托管策略。

Namespaces

Linux命名空间是另一个有用的抽象层。命名空间允许我们拥有许多进程层次,每个层次都有自己的嵌套“子树(subtree)”。命名空间可以使用全局资源,并将其呈现给其成员,就像它是自己的资源一样。

具体来看,Linux系统开始时的进程标识符(PID)为1,并且所有其他进程将包含在其树中。PID命名空间允许我们跨越一棵新树,它拥有自己的PID 1进程。现在有两个值为1的PID,每个命名空间可以产生自己的命名空间,并且相同的过程可以附加了几个PID。

子命名空间中的一个进程将不知道父级的进程存在,而父命名空间将可以访问整个子命名空间。

有七种类型的名称空间:cgroup、IPC、网络、mount、PID、用户和UTS。

Network Namespace

一些资源是稀缺的。按照惯例,有些端口具有预定义的角色,不应用于其他任何用途:端口80仅服务于HTTP调用,端口443仅服务于HTTPS调用等等。在共享主机环境中,两个或多个站点可以监听来自端口80的HTTP请求。第一个获得该端口的站点不允许任何其他应用程序访问该端口上的数据。第一个应用程序在互联网上是可见的,而其他所有应用程序将不可见。

解决方案是使用网络命名空间,通过网络命名空间,内部进程将看到不同的网络接口。

在一个网络命名空间中,同一端口可以是开放的,而在另一个网络命名空间中,可以关闭该端口。为此,我们必须采用额外的“虚拟”网络接口,这些接口同时属于多个命名空间。中间还必须有一个路由器进程,将到达物理设备的请求连接到相应的名称空间和其中的进程。

复杂吗?这就是为什么Docker和类似工具如此受欢迎。现在让我们来介绍一下Docker,以及它的替代方案。

Docker:人人可用的容器

在容器统治云计算世界之前,虚拟机非常流行。如果你有一台Windows机器,但想为iOS开发移动应用程序,你可以购买一台新的Mac,或者将其虚拟机安装到Windows硬件上。虚拟机也可能是笨重的,它们经常吞噬不需要的资源,而且启动速度通常很慢(长达一分钟)。

容器是标准软件单元,具有运行程序所需的一切:操作系统、数据库、镜像、图标,软件库、代码和所需的其他组件。容器的运行也与所有其他容器,甚至与操作系统本身隔离。与虚拟机相比,容器是轻量级的,所以它们可以快速启动,并且容易被替换。

要运行隔离和保护,容器需要基于Chroot、cgroups和命名空间。

容器的镜像是在实际机器上形成应用程序的模板,能够根据单个镜像创建尽可能多的容器,一个名为Dockerfile的文本文件包含了组装镜像所需的所有信息。

Docker带来的真正革命是创建了Docker镜像仓库和开发了Docker引擎,这些镜像以相同的方式在各地运行,作为第一个被广泛采用的容器镜像,形成了一个不成文的世界标准,所有后来的入局者都必须关注它。

CRI and OCI

1.jpeg

OCI 全称为Open Container Initiative,它发布镜像和容器的规范。它于2015年由Docker发起,并被微软、Facebook、英特尔、VMWare、甲骨文和许多其他行业巨头接受。

OCI还提供了规范的一个实现,被称为 runc,它可以直接使用容器,创建并运行它们等。

容器运行时接口(Container Runtime Interface,简称CRI)是一个Kubernetes API,它定义了Kubernetes如何与容器运行时交互。它也是标准化的,所以我们可以选择采用哪个CRI实现。

用于CRI和OCI的容器的软件堆栈

Linux是运行容器的软件堆栈中最基本的部分:
2.jpeg

请注意,Containerd和CRI-O都坚持CRI和OCI规范。对于Kubernetes而言,这意味着它可以使用Containerd或CRI-O,而用户不会注意到其中的区别。它还可以使用我们现在要提到的任何其他替代方案——这正是创建和采用了OCI和CRI等软件标准的目标。

Docker软件堆栈

Docker的软件堆栈包括:
  • docker-cli,面向开发者的Docker命令行界面
  • containerd,最初由Docker编写,后来作为一个独立的项目启动; 它实现了CRI规范
  • runc,它实现了OCI规范
  • 容器(使用chroot、cgroups、命名空间等)


Kubernetes的软件堆栈几乎是相同的;Kubernetes使用CRI-O,而不是Containerd,这是由Red Hat / IBM和其他人创建的CRI实现。

containerd

3.jpeg

containerd作为一个守护程序在Linux和Windows上运行。它加载镜像,将其作为容器执行,监督底层存储,并负责整个容器的运行时间和生命周期。

Containerd诞生于2014年,一开始作为Docker的一部分,2017年成为云原生计算基金会(CNCF)中的一个项目,并于 2019年年初毕业

runc

runc是OCI规范的参考实现。它创建并运行容器以及其中的进程。它使用较低级别的Linux特性,比如cgroup和命名空间。

runc的替代方案包括Kata-Runtime、GVisor和CRI-O。

Kata-Runtime使用硬件虚拟化作为单独的轻量级VM实现OCI规范。它的运行时与OCI、CRI-O和Containerd兼容,因此它可以与Docker和Kubernetes无缝工作。
4.jpeg

Google的gVisor创建包含自己内核的容器。它通过名为 runsc的项目实现OCI,该项目与Docker和Kubernetes集成。有自己内核的容器比没有内核的容器更安全,但它不是万能的,而且这种方法在资源使用上要付出代价。
5.jpeg

CRI-O是一个纯粹为Kubernetes设计的容器堆栈,是CRI标准的第一个实现。它从任何容器镜像仓库中 提取镜像,可以作为使用Docker的轻量级替代方案。
6.jpeg

今天它支持runc和Kata Containers作为容器运行时,但也可以插入任何其他OC兼容的运行时(至少在理论上)。

它是一个CNCF孵化项目。

Podman

7.jpeg

Podman是一个没有守护进程的Docker替代品。它的命令有意与Docker尽可能兼容,以至于您可以在CLI界面中创建一个别名并开始使用单词“Docker”而不是“podman”。

Podman的目标是取代Docker,因此坚持使用相同的命令集是有意义的。Podman试图改进Docker中的两个问题。

首先,Docker总是使用内部守护进程执行。守护进程是在后台运行的单进程。如果它失败了,整个系统就会失败。

第二,Docker作为后台进程运行,具有root权限,所以当你给一个新的用户访问权时,你实际上是给了整个服务器的访问权。

Podman是一个远程Linux客户端,可直接从操作系统运行容器。你也可以以rootless模式运行它们。它从DockerHub下载镜像,并以与Docker完全相同的方式运行它们,具有完全相同的命令。

Podman以root以外的用户身份运行命令和镜像,所以它比Docker更安全。另一方面,有许多为Docker开发的工具在Podman上是不可用的,如Portainer和Watchtower。摆脱Docker意味着放弃你之前建立的工作流程。

Podman的目录结构与buildah、skopeo和CRI-I类似。它的Pod也非常类似于KubernetesPod。

Linux容器:LXC和LXD

LXC(LinuX Containers)于2008年推出,是Linux上第一个上游内核的容器。Docker的第一个版本使用了LXC,但在后来的发展中,由于已经实现了 runc,所以LXC被移除了。

LXC的目标是使用一个Linux内核在一个控制主机上运行多个隔离的Linux虚拟环境。为此,它使用了cgroups功能,而不需要启动任何虚拟机;它还使用命名空间,将应用程序与底层系统完全隔离。

LXC旨在创建系统容器,几乎就像你在虚拟机中一样——但硬件开销很小,因为这些硬件是被虚拟化的。

LXC不模拟硬件和软件包,只包含需要的应用程序,所以它几乎以裸机速度执行。相反,虚拟机包含整个操作系统,然后模拟硬件,如硬盘、虚拟处理器和网络接口。

所以,LXC是小而快的,而虚拟机是大而慢的。另一方面,虚拟环境不能被打包成现成的、可快速部署的机器,而且很难通过GUI管理控制台进行管理。LXC要求技术人员有很高的技术水平,并且优化后的机器可能与其他环境不兼容。

LXC VS Docker

LXC就像Linux上的一个增压chroot,它产生的“小”服务器启动更快,需要更少的RAM。然而,Docker提供了更多特性:
  • 跨机器的可移植部署:使用一个版本的Docker创建的对象可以传输并安装到任何其他支持Docker的Linux主机上。
  • 版本控制:Docker可以用一种类似git的方式跟踪版本——您可以创建容器的新版本,将它们回滚等等。
  • 重复使用组件:使用Docker,您可以将已经创建的包堆叠到新包中。如果您想要一个LAMP环境,可以安装一次它的组件,然后将它们作为预先制作的LAMP镜像重新使用。
  • Docker镜像存档:可以从专用站点下载数十万个Docker镜像,并且很容易将新镜像上传到这样的镜像仓库中。


LXC面向系统管理员,而Docker更面向开发人员。这就是Docker更受欢迎的原因所在。

LXD

LXD有一个特权守护进程,它通过本地UNIX socket和网络(如果启用)公开REST API。您可以通过命令行工具访问它,但它总是使用REST API调用进行通信。无论客户端是在本地机器上还是在远程服务器上,它的功能都是一样的。

LXD可以从一台本地机器扩展到几千台远程机器。与Docker类似,它是基于镜像的,所有更流行的Linux发行版都可以使用镜像。Ubuntu的公司Canonical正在资助LXD的开发,因此它将始终运行在Ubuntu以及其他类似Linux操作系统的最新版本上。LXD可以与OpenNebula和OpenStack标准无缝集成。

从技术上讲,LXD是站在LXC的肩膀上(两者都使用相同的liblxc库和Go语言创建容器),但LXD的目标是改善用户体验。

Docker会永远存在吗?

Docker拥有1100万开发者、700万个应用程序和每月130亿次的镜像下载。如果仅仅说Docker仍然是领导,那就太轻描淡写了。然而,在这篇文章中,我们已经看到,现在已经有许多产品可以取代Docker软件栈的一个或多个部分,并且通常情况下没有兼容性问题。而且与Docker提供的服务相比,其他软件的主要目标是安全性。

原文链接: https://mp.weixin.qq.com/s/o_sqMDTS5JGuD-gu0RxDEg

    Top 18 开源低代码开发平台

    $
    0
    0

    与使用计算机编程语言构建应用程序的传统方法不同,低代码开发平台是使用图形向导来创建和构建软件的应用程序开发平台。因此,在许多情况下,低代码或无代码(几乎没有代码)这个名称是作为可视化开发工具来帮助设计人员进行拖放、组件浏览器和逻辑构建器的。

    低代码/无代码的主要概念并不新鲜,它可以追溯到十多年前的无代码编程 (PWCT) 和类似系统。但是,这一概念在开发者社区中并没有那么可用或得到支持。如今,数十种低代码/无代码平台和服务涌入互联网;因为事实证明,这一概念不仅仅适用于快速项目的原型设计。

    外媒  Medevel 整理介绍了供个人和企业使用的最佳开源低代码和无代码平台列表。具体如下:

    1、Saltcorn

    Saltcorn UI 构建器

    Saltcorn 是一个无代码数据库管理器 Web 应用程序。它配备了一个引人注目的仪表板、丰富的生态系统和视图构建器以及可主题化的界面。几乎没有编码经验的用户可以在几分钟内构建一个丰富的交互式数据库应用程序。公司也可以使用它来创建日常使用的工具并即时重新塑造它们。

    Saltcorn 有一个令人印象深刻的示例应用程序列表,其中包括:博客、地址簿、项目管理系统、问题跟踪器、wiki、团队管理等。Saltcorn 采用 MIT 许可作为免费开源项目发布。感兴趣的用户可以点击 这个链接运行在线演示。

    Saltcorn 官方地址: https://github.com/saltcorn/saltcorn

    2、Joget DX

    Joget DX 是一个低代码应用程序构建平台,可简化公司的数字化转型。它将业务流程自动化管理、工作流定制与低代码应用程序开发工具相结合。

    Joget DX 可以在云端和本地运行。它有丰富的文档、易于使用的仪表板和可视化构建器,支持拖放和独立于操作系统和数据库。

    3、Digdag

    Digdag 是一款开源企业解决方案,旨在实现易于部署、多云设置和模块化的结构来构建和扩展业务应用。Digdag 拥有一系列企业功能,包括丰富的管理面板、多语言支持、错误处理、配置工具和版本控制工具。该解决方案采用Java和Node.js开发,支持 AWS、私有云、IBM 云和 Digital Ocean。​​​​

    4、Stackstorm

    Stackstorm 与此列表中的任何其他系统不同,它被设计为在一个平台内连接、管理和监控企业应用程序的 umbrella。

    Stackstorm 专注于事件驱动方法、自定义工作流设计以及用户角色和权限。

    方法很简单:事件驱动的自动化,通过丰富的日志记录系统提供传感器、触发器、每个定义的操作、规则、工作流和审计。

    5、CUBA Platform

    CUBA 平台是一个面向企业的开源(Apache 2.0)快速应用开发系统。它带有数十种工具作为 IDE、应用程序构建工作室、CLI 命令行界面和可靠的可扩展基础设施。CUBA 平台有一个丰富的插件系统,其中包含一个 BPM(业务流程管理)附加组件,需要花费一些时间来构建和安装。

    6、Skyve

    Skyve 是一个开源的业务软件构建平台。它支持无代码和低代码的快速应用开发。支持不同的数据库引擎:MySQL、SQL 服务器和 H2 数据库引擎。其开发人员目前正在努力支持PostgreSQL和Oracle。Skyve提供了丰富的 API 集,以及低代码开发应用构建向导。

    Skyve 平台由丰富的生态系统组成,其中包括:

    1. 企业平台,
    2. 构建器应用程序,采用React Native构建原生移动应用,
    3. 与其他第三方服务集成的 Skyve 总线模块,
    4. Skyve Confidence:为TDD提供测试功能
    5. Skyve Cortex:
    6. Skyve  Portal:企业应用的 Web 门户扩展
    7. Skyve CRM:自定义构建Skyve CRM 应用程序
    8. Skyve Replica:在分布式 Skyve 实例之间提供无缝同步

    7、Rintagi

    Rentagi 是一个专注于移动的低代码企业级应用程序构建平台。它也是完全免费和开源的解决方案,这使其成为中小型公司的完美解决方案。配备了丰富的复杂工具,可用于快速构建应用程序以提高生产力,它还为移动开发人员提供了丰富的开发人员友好的 API。

    8、Opexava

    OpenXava 是一个低代码应用程序构建平台,主要关注生产力、简单性和可用性。作为一个使用 Java 技术构建的跨平台系统,它运行在 Linux 和 Windows 服务器上。它可能看起来像一个遗留系统(stated 2005),但它仍然是许多企业的首选。

    OpenXava 确保了高生产力、较短的功能学习曲线、大量的企业功能以及完整的移动和平板电脑响应式布局。 OpenXava 是一个免费的开源社区版,但企业可以购买不同的额外功能版本。

    9、Convertigo

    Convertigo 是无代码和低代码平台的混合体。它旨在帮助公民开发人员和认真的开发人员在短时间内创建企业级应用程序和工具。为开发人员提供本地安装、云版本和 MBaaS 版本。它具有移动构建器工具、可视化拖放 UI、低代码后端、REST/XML 转换器、REST/JSON 转换器、管理控制台等。

    Convertigo 提供完整的 PWA(渐进式 Web 应用程序)、iOS 和 Android 移动开发支持。

    10、Tymly

    Tymly 是一个业务优先的低代码平台,用于创建可扩展的服务器应用程序。它是在 MIT 许可下作为开源项目发布的。

    Tymly 引入了蓝图概念,将业务流程、功能和工作流封装到蓝图中。它有一个生态系统和蓝图存储,可以保护大量的开发资源。蓝图保存在 JSON 模式中,而数据保存在 PostgreSQL 数据库中。开发人员可以通过在 JSON 模式中定义他们的需求、业务功能和工作流来编写他们的蓝图。

    11、JUDO

    JUDO.codes 是另一个面向企业使用的低代码平台。在数据建模、UI 设计和开发方面,JUDO 为开发人员提供了比此列表中其他低代码平台更大的灵活性。

    JUDO 可用于 Windows、macOS 和 Linux 的可安装包和二进制应用程序包。(* macOS 安装程序即将推出)。

    JUDO.code 门户为新手提供了丰富的文档和一套教程。它还提供了广泛的用户指南以及应用程序平台的安装说明。

    12、OpenCatapult

    OpenCatapult 是一个开源的低代码 DevOps 自动化平台。它通过动态管理自动化的例行任务,帮助 DevOps 和服务器管理员更好地控制他们的服务器。

    不过,OpenCatapult 仅适用于 Windows x64 平台,但开发人员正在计划在不久的将来发布 Linux 和 macOS 版本。

    13、BudiBase

    BudiBase 不仅仅是另一个低代码平台,它与这里的其他平台有着不同的目标,因为它专注于为开发人员提供工具,以加快一个平台内的开发、部署和集成过程。

    14、Generative Objects(GO)Platform

    Go Platform  是一家法国公司,致力于企业低代码开发平台。侧重于更好地控制应用程序生命周期、开发人员之间的协作,以及与其他平台、桌面、移动和 Web 开发的软件集成。

    Go Platform 采用基于模型的应用程序开发方法,在专注于解决方案而非工具时节省时间、金钱和精力。

    15、Baserow

    Baserow 是一种用于动态创建、管理数据库和构建数据库应用程序的迷人工具。它具有确保高生产力和可用性的功能。

    因为  Baserow 是一个模块化系统,它提供了一个完整的 REST-API 无头系统,所以它吸引了移动开发人员的注意,将其用作他们应用程序的后端。Baserow 正在积极开发中。

    16、OS.bee

    OS.bee 是一个免费的企业开源平台,它将低代码和无代码与模型驱动的应用程序开发策略相结合。该平台由一家专业打造企业ERP系统30多年的公司打造。

    OS.bee 提供了用于创建应用程序数据模型的可视化模型和图表构建器。该系统是用 Java 创建的,并提供 Eclipse 安装和集成。

    17、nuBuilder

    nuBuilder 是一个免费的开源 RAD(快速应用程序开发),用于在几分钟内构建企业数据库应用程序。它是自托管的、低代码的,具有高度可定制的后端系统。它使用 PHP、HTML 和 JavaScript 和 MySQL 数据库。

    18、Metabase

    Metabase 是一个开源的面向数据的可定制仪表板,支持广泛的数据库后端,如 MongoDB、MySQL、PostgreSQL、SQL Server、Oracle 等。它提供了一个用于管理数据库记录、操作数据、操作记录的可视化方法、支持连接、多重聚合、高级过滤和全文搜索的层。它是在几分钟内为企业创建具有高生产力和可用性的高效数据库就绪仪表板的终极解决方案。

    Metabase 包含令人惊叹的视觉小部件,其中包括:图表、地图、SVG 矢量地图、分析以及为用户、开发人员和 DevOps 提供的丰富的详细文档。

    在此处查看整个功能列表

    开源OLAP引擎哪个快? (Presto、HAWQ、ClickHouse、GreenPlum) - 知乎

    $
    0
    0

    易观CTO 郭炜 序
    现在大数据组件非常多,众说不一,在每个企业不同的使用场景里究竟应该使用哪个引擎呢?这是易观Spark实战营出品的开源Olap引擎测评报告,团队选取了Hive、Sparksql、Presto、Impala、Hawq、Clickhouse、Greenplum大数据查询引擎,在原生推荐配置情况下,在不同场景下做一次横向对比,供大家参考。
    每年易观都会发布一次这样的大数据开源测评报告,欢迎大家给出更好的测评意见以及想要测试的组件。易观Spark实战营是易观大数据技术团队组织的针对大数据初学者的实战训练营,欢迎搜索访问“易观数据极客社区”,在文章后留言,交流最新最全的大数据技术。

    目录

    开源OLAP框架基线测试报告一、测试方案1.1 测试整体方案1.2 TPC-DS测试与单表测试方案及数据准备1.3 环境准备二、测试组件介绍2.1 SparkSql2.2 Presto2.3 Impala2.4 HAWQ2.5 ClickHouse2.6 Hive2.7 Greenplum三、性能测试分析3.1 数据压缩3.2 性能测试3.2.1 多表关联查询对比测试3.2.2 单表查询对比测试3.3 性能测试结果分析四、各组件综合分析比较

    一、测试方案

    1.1测试整体方案

    本次测试针对现有Olap的7大Sql引擎Hadoop(2.7)、Hive(2.1)、Hawq(3.1.2.0)、Presto(0.211)、Impala(2.6.0)、Sparksql(2.2.0)、Clickhouse(18.1.0-1.El7)、Greenplum(5.7.0) 基础性能测试。我们采用多表关联和单大表性能分别对比不同组件在查询性能、系统负载等方面的情况,测试方案如下:

    1. 1,多表关联采用Tpc-Ds基准测试工具生成相应测试语句和数据进行测试,
    2. 2,单大表测试同样选用Tpc-Ds基准测试工具生成的最大数据量的表,并采用我们选用的一些常规性聚合语句进行测试。

    1.2 TPC-DS测试与单表测试方案及数据准备

    TPC-DS采用星型、雪花型等多维数据模式。它包含7张事实表,17张维度表平均每张表含有18列。其工作负载包含99个SQL查询,覆盖SQL99和2003的核心部分以及OLAP。这个测试集包含对大数据集的统计、报表生成、联机查询、数据挖掘等复杂应用,测试用的数据和值是有倾斜的,与真实数据一致。可以说TPC-DS是与真实场景非常接近的一个测试集,也是难度较大的一个测试集。

    TPC-DS的这个特点跟大数据的分析挖掘应用非常类似。Hadoop等大数据分析技术也是对海量数据进行大规模的数据分析和深度挖掘,也包含交互式联机查询和统计报表类应用,同时大数据的数据质量也较低,数据分布是真实而不均匀的。因此TPC-DS成为客观衡量多个不同Hadoop版本以及SQL on Hadoop技术的最佳测试集。

    本次测试采用TPC-DS提供的dsdgen命令工具生成指定量级的测试数据,我们指定数据量级为100G。

    生成的各个表的数据量如下:


    通过使用dsqgen命令根据TPC-DS提供的模板生成不同类型的SQL语句,TPC-DS默认支持以下模板:db2.tpl、netezza.tpl、oracle.tpl、sqlserver.tpl。我们通过命令生成sqlserver.tpl模板的SQL语句,分别对其进行细微的修改使其符合不同的olap引擎支持的语法规则。

    对于多表关联测试,我们从中选取了15条有代表性的sql语句(见附件二),几乎所有的测试案例都有很高的IO负载和CPU计算需求,涵盖了几乎所有的业务场景。

    对于单大表测试,我们选择TPC-DS生成的测试数据集中数据量最大的表store_sales,并选用了9条使用频率高的常规性聚合sql语句进行测试(见附件三)。

    1.3环境准备

    本次测试方案的硬件环境使用三台物理机,操作系统为centos7,基础配置信息如下表:

    服务器cpu核数cpu线程数内存大小磁盘空间server141664g2Tserver241664g2Tserver341664g2T

    本次测试各组件搭建的版本信息如下:Hadoop(2.7)、Hive(2.1)、HAWQ(3.1.2.0)、Presto(0.211)、Impala(2.6.0)、sparksql(2.2.0)、clickhouse(18.1.0-1.el7)、greenplum(5.7.0),所有组件都采用分布式搭建于三台服务器,并配置每台服务器上查询最大使用内存20g,cpu 8线。

    各个Olap引擎通过各自的方式创建表结构,导入数据。Hive使用Orc格式的内部表;Impala使用Hive上的Parquet格式数据;Presto使用Hive上的Orc格式数据;Hawq建立内部表使用默认Txt格式;Clickhouse使用Log表引擎分布式建表。

    二、测试组件介绍

    2.1 SparkSql

    Spark SQL 是 Spark 处理结构化数据的程序模块。它将 SQL 查询与 Spark 程序无缝集成,可以将结构化数据作为 Spark 的 RDD 进行查询。RDD 的全称为 Resilient Distributed Datasets,即弹性分布式数据集,是 Spark 基本的数据结构。Spark 使用 RDD 作为分布式程序的工作集合,提供一种分布式共享内存的受限形式。RDD 是只读的,对其只能进行创建、转化和求值等操作。SparkSQL作为Spark生态的一员继续发展,而不再受限于Hive,只是兼容Hive。我们利用hive作为数据源,spark作为计算引擎,通过SQL解析引擎,实现基于hive数据源,spark作为计算引擎的SQL测试方案。

    2.2 Presto

    Presto是一个分布式SQL查询引擎, 它被设计为用来专门进行高速、实时的数据分析。它支持标准的ANSI SQL,包括复杂查询、聚合(aggregation)、连接(join)和窗口函数(window functions)。作为Hive和Pig(Hive和Pig都是通过MapReduce的管道流来完成HDFS数据的查询)的替代者,Presto 本身并不存储数据,但是可以接入多种数据源,并且支持跨数据源的级联查询。Presto是一个OLAP的工具,擅长对海量数据进行复杂的分析;但是对于OLTP场景,并不是Presto所擅长,所以不要把Presto当做数据库来使用。

    2.3 Impala

    Impala 是 Cloudera 在受到 Google 的 Dremel 启发下开发的实时交互SQL大数据查询工具,它拥有和Hadoop一样的可扩展性、它提供了类SQL(类Hsql)语法,在多用户场景下也能拥有较高的响应速度和吞吐量。它是由Java和C++实现的,Java提供的查询交互的接口和实现,C++实现了查询引擎部分,除此之外,Impala还能够共享Hive Metastore,甚至可以直接使用Hive的JDBC jar和beeline等直接对Impala进行查询、支持丰富的数据存储格式(Parquet、Avro等)。此外,Impala 没有再使用缓慢的 Hive+MapReduce 批处理,而是通过使用与商用并行关系数据库中类似的分布式查询引擎(由 Query Planner、Query Coordinator 和 Query Exec Engine 三部分组成),可以直接从 HDFS 或 HBase 中用 SELECT、JOIN 和统计函数查询数据,从而大大降低了延迟。

    2.4 HAWQ

    HAWQ 是一个 Hadoop 上的 SQL 引擎,是以 Greenplum Database 为代码基础逐渐发展起来的。HAWQ 采用 MPP 架构,改进了针对 Hadoop 的基于成本的查询优化器。除了能高效处理本身的内部数据,还可通过 PXF 访问 HDFS、Hive、HBase、JSON 等外部数据源。HAWQ全面兼容 SQL 标准,能编写 SQL UDF,还可用 SQL 完成简单的数据挖掘和机器学习。无论是功能特性,还是性能表现,HAWQ 都比较适用于构建 Hadoop 分析型数据仓库应用。

    2.5 ClickHouse

    Clickhouse由俄罗斯yandex公司开发。专为在线数据分析而设计。Yandex是俄罗斯搜索引擎公司。官方提供的文档表名,ClickHouse 日处理记录数”十亿级”。

    特性:采用列式存储;数据压缩;基于磁盘的存储,大部分列式存储数据库为了追求速度,会将数据直接写入内存,按时内存的空间往往很小;CPU 利用率高,在计算时会使用机器上的所有 CPU 资源;支持分片,并且同一个计算任务会在不同分片上并行执行,计算完成后会将结果汇总;支持SQL,SQL 几乎成了大数据的标准工具,使用门槛较低;支持联表查询;支持实时更新;自动多副本同步;支持索引;分布式存储查询。

    2.6 Hive

    Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。

    Hive是建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具,可以用来进行数据提取转化加载(ETL),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL,它允许熟悉 SQL 的用户查询数据。同时,这个语言也允许熟悉 MapReduce 开发者的开发自定义的 mapper 和 reducer 来处理内建的 mapper 和 reducer 无法完成的复杂的分析工作。

    2.7 GreenPlum

    Greenplum是一个开源的大规模并行数据分析引擎。借助MPP架构,在大型数据集上执行复杂SQL分析的速度比很多解决方案都要快。

    GPDB完全支持ANSI SQL 2008标准和SQL OLAP 2003 扩展;从应用编程接口上讲,它支持ODBC和JDBC。完善的标准支持使得系统开发、维护和管理都大为方便。支持分布式事务,支持ACID。保证数据的强一致性。做为分布式数据库,拥有良好的线性扩展能力。GPDB有完善的生态系统,可以与很多企业级产品集成,譬如SAS,Cognos,Informatic,Tableau等;也可以很多种开源软件集成,譬如Pentaho,Talend 等。

    三、性能测试分析

    3.1 数据压缩

    数据压缩方面,Sparkql、Impala、Presto均采用的是hive元数据,hive数据100G上传之后显示为96.3G(.dat数据格式),压缩比0.963;hawq压缩后数据大小为68.2G(.dat格式),压缩比:0.682;clickhouse采用自己默认格式42G;greenplum未使用压缩,数据存储大小为98G。

    3.2 性能测试

    本节通过查询语句对比SparkSql、Presto、Impala、HAWQ、ClickHouse、Hive、GreenPlum七种组件的查询性能,测试结果均采用连续三次查询结果的平均值,通过图表展示对比结果。

    性能分析部分我们分为两部分,第一部分是多表关联查询对比测试,第二部分是单大表查询对比测试。

    3.2.1 多表关联查询对比测试

    以下是多表关联测试结果,数据如下(sql文件见附件二):



    通过我们选取的15条sql语句查询测试,从表中可以看出,presto、impala和hawq查询时间快于SparkSql和ClickHouse,性能约是SparkSql的2-3倍,其中尤其以Presto和Impala性能要好一些。greenplum在多表查询上也有不错的表现;ClickHouse对于多表join效果相比较于Presto、Impala、HAWQ不是很好,并且很多复杂语法支持的不够好,可见并不是为关联分析而设置;而hive无疑是所有组件中耗时最多的,其中部分语句查询时间超出1h的总时间按1h计算。

    下面是通过图形展示来更加直观比较各组件性能。由于hive与其他相差太大,在图中不作比较。




    3.2.2 单表查询对比测试

    以下是9条单表测试语句对六种组件进行测试,测试结果图表分析如下(查询sql见附件三):


    从结果中我们发现,对于单表测试ClickHouse要比其余几种组件查询速度表现突出,测试性能约是其余四种的3-6倍。而Presto相比于HAWQ、Impala、SparkSql、GreenPlum在单表操作方面性能也稍好些。

    下面通过图来直观比较:





    从图像上更加清楚地显示出五种组件在单表测试方面性能的差距,Clickhouse在性能方面体现出了足够的优势,在单大表查询方面比其余组件性能都要好;Impala和Presto相比较,在sql_01-sql_05这5条语句是简单的一些求和求平均的单表操作方面,Presto的性能要比Impala好很多,而sql_06-sql_09一些复杂点的多个列的单表操作,Impala的性能要比Presto好一些,这也反映出Presto更加适合一些简单的数据量大的聚合操作,而Impala适合一些复杂的聚合操作。

    最后我们发现HAWQ、GreenPlum在单表聚合操作方面性能不如其余四种组件,测试时间要大大超过它们,当然也不排除测试环境的影响,但是测试结果表明,HAWQ、GreenPlum不适合单表的复杂聚合操作,更适合多表的聚合操作。

    3.3 性能测试结果分析

    从上面的分析结果可以看出,presto、Impala以及hawq在多表查询方面体现出了优势,虽说presto和Impala在多表查询方面的性能差别不大,但是在查询过程中却发现Impala的一些局限性,并尽量避开这些局限问题进行测试。Impala不支持的地方,例如:不支持update、delete操作,不支持Date数据类型,不支持ORC文件格式等等,而presto则基本没有这些局限问题(本次测试中基本没有发现)。

    在单表测试方面clickhouse体现出了比其余组件的优势,性能比其他组件要好一大截,而presto相比于hawq和impala以及sparksql在单大表聚合操作方面的表现也相对优秀。

    四、各组件综合分析比较

    通过以上图表查询性能分析以及我们查找相关资料对各组件总结如下:

    1. SparkSQL是Hadoop中另一个著名的SQL引擎,它以Spark作为底层计算框架,Spark使用RDD作为分布式程序的工作集合,它提供一种分布式共享内存的受限形式。在分布式共享内存系统中,应用可以向全局地址空间的任意位置进行读写操作,而RDD是只读的,对其只能进行创建、转化和求值等操作。这种内存操作大大提高了计算速度。SparkSql的性能相对其他的组件要差一些,多表单表查询性能都不突出。
    2. Impala官方宣传其计算速度是一大优点,在实际测试中我们也发现它的多表查询性能和presto差不多,但是单表查询方面却不如presto好。而且Impala有很多不支持的地方,例如:不支持update、delete操作,不支持Date数据类型,不支持ORC文件格式等等,所以我们查询时采用parquet格式进行查询,而且Impala在查询时占用的内存很大。
    3. Presto综合性能比起来要比其余组件好一些,无论是查询性能还是支持的数据源和数据格式方面都要突出一些,在单表查询时性能靠前,多表查询方面性能也很突出。由于Presto是完全基于内存的并行计算,所以presto在查询时占用的内存也不少,但是发现要比Impala少一些,比如多表join需要很大的内存,Impala占用的内存比presto要多。
    4. HAWQ 吸收了先进的基于成本的 SQL 查询优化器,自动生成执行计划,可优化使用hadoop 集群资源。HAWQ 采用 Dynamic pipelining 技术解决这一关键问题。Dynamic pipelining 是一种并行数据流框架,利用线性可扩展加速Hadoop查询,数据直接存储在HDFS上,并且其SQL查询优化器已经为基于HDFS的文件系统性能特征进行过细致的优化。但是我们发现HAWQ在多表查询时比Presto、Impala差一些;而且不适合单表的复杂聚合操作,单表测试性能方面要比其余四种组件差很多,hawq环境搭建也遇到了诸多问题。
    5. ClickHouse 作为目前所有开源MPP计算框架中计算速度最快的,它在做多列的表,同时行数很多的表的查询时,性能是很让人兴奋的,但是在做多表的join时,它的性能是不如单宽表查询的。性能测试结果表明ClickHouse在单表查询方面表现出很大的性能优势,但是在多表查询中性能却比较差,不如presto和impala、hawq的效果好。
    6. GreenPlum作为关系型数据库产品,它的特点主要就是查询速度快,数据装载速度快,批量DML处理快。而且性能可以随着硬件的添加,呈线性增加,拥有非常良好的可扩展性。因此,它主要适用于面向分析的应用。比如构建企业级ODS/EDW,或者数据集市等,GREENPLUM都是不错的选择。
    7. 此外我们还对flink进行了调研发现,flink 核心是个流式的计算引擎,通过流来模拟批处理,flink sql还处于早期开发阶段,未来社区计划通过提供基于REST的SQL客户端,目前sql客户端不能直接访问hive,通过YAML file文件定义外部数据源,可以连接文件系统和kafka,目前短时间我们的sql测试不太好模拟。所以没有对flink进行测试分析。
      我们通过测试以及以上的相关调研编写了各组件各个方面的综合对比分析表,这里采用5分为满分来比较,如下表:



    附件信息:

    附件一: 表结构信息

    附件二: TPC-DS查询测试语句

    附件三: 单表查询测试语句

    附件四: 性能对比表(Sheet1:TPC-DS语句,Sheet2:单表语句,Sheet5:改变格式优化之后的测试结果)

    在原神里钓鱼,有人竟然用上了深度强化学习,还把它开源了

    $
    0
    0

    还愁在《原神》里钓不到鱼吗?这有一份迟到的提瓦特钓鱼指南。

    在游戏圈,你可以没有玩过,但一定听过《原神》。

    虽然这是一款口碑两极分化的游戏,但不得不承认《原神》是当前最为火热的游戏之一。

    特别是在国外,原神可以说是火的一塌糊涂。

    就在今年 9 月,这款从开放公测起便屡次登顶国内外讨论热度和手游吸金榜第一的开放世界冒险游戏更新了版本,添加 / 丰富了地图,并且上线了一款小游戏——钓鱼。游戏中多个水域都有钓鱼点,不同的位置可以钓不同的鱼。

    尽管是再普通不过的玩法,还是引得玩家流连。一般来说,钓鱼一共分三个步骤:甩杆→等待鱼儿上钩→提竿。其中所涉及的原理需要一定数字图像处理与机器学习基础。模型分为鱼群定位与识别和拉杆 (和鱼博弈) 两个部分。

    很多玩家都在寻找钓鱼攻略,你还在愁在《原神》里钓不到鱼吗?今天我们为你送上这份迟到的提瓦特钓鱼指南。

    这份钓鱼指南可以说是完全解放双手,不需要任何操作,只需要启动程序就能完成。上线短短几天,收获 700 + 星。

    GitHub 地址:https://github.com/7eu7d7/genshin_auto_fish

    感兴趣的小伙伴也可以去 B 站观看视频,上线不到三天,超过 44 万次播放量。这满屏的弹幕,不禁让人直呼离谱。

    已经有网友开始上手了,并评论道:已经在部署了,连夜下载了 anaconda。

    B 站地址:https://www.bilibili.com/video/BV1964y1b7vV?spm_id_from=333.905.b_7570566964656f.3

    项目介绍

    原神自动钓鱼 AI 由两部分模型组成:YOLOX、DQN。此外,该项目还用到了迁移学习,半监督学习来进行训练。模型也包含了一些使用 opencv 等传统数字图像处理方法实现的不可学习部分。
    • YOLOX 用于鱼的定位和类型的识别以及鱼竿落点的定位;

    • DQN 用于自适应控制钓鱼过程的点击,让力度落在最佳区域内。


    安装

    该项目是在 python 运行环境中使用的,需要先安装 python,这里推荐使用 anaconda。

    配置环境:打开 anaconda prompt(命令行界面),创建新的 python 环境并激活(推荐 python3.7 或以下版本):

    conda create -n ysfish python=3.6conda activate ysfish

    下载工程代码:使用 git 下载,或直接在 github 网页端下载后直接解压:

    git clone https://github.com/7eu7d7/genshin_auto_fish.git

    依赖库安装:切换命令行到本工程所在目录:

    cd genshin_auto_fish

    执行以下命令安装依赖:

    python -m pip install -U pippython requirements.py

    如果要使用显卡进行加速需要安装 CUDA 和 cudnn, 安装后无视上面的命令用下面这条安装 gpu 版:

    pip install -U pippython requirements.py --cuda [cuda 版本]# 例如安装的 CUDA11.xpython requirements.py --cuda 110

    安装 yolox:切换命令行到本工程所在目录,执行以下命令安装 yolox:

    python setup.py develop

    预训练权重下载:下载预训练权重 (.pth 文件),yolox_tiny.pth 下载后将权重文件放在 工程目录 / weights 下。

    YOLOX 训练工作流程:YOLOX 部分用半监督学习打标签。标注少量样本后训练模型生成其余样本伪标签再人工修正,不断迭代以提高精度。样本量较少所以使用迁移学习,在 COCO 预训练的模型上进行 fine-tuning。

    将 yolox/exp/yolox_tiny_fish.py 中的 self.data_dir 的值改为解压后 2 个文件夹所在的路径。

    训练代码:

    python yolox_tools/train.py -f yolox/exp/yolox_tiny_fish.py -d 1 -b 8 --fp16 -o -c weights/yolox

    DQN 训练工作流程:控制力度使用强化学习模型 DQN 进行训练。两次进度的差值作为 reward 为模型提供学习方向。模型与环境间交互式学习。

    直接在原神内训练耗时较长,首先你需要制作一个仿真环境,大概模拟钓鱼力度控制操作。在仿真环境内预训练一个模型。随后将这一模型迁移至原神内,实现域间迁移。

    仿真环境预训练代码:

    python train_sim.py

    原神游戏内训练:

    python train.py

    运行

    以上准备就绪后,就可以运行钓鱼 AI,注意命令行窗口一定要以管理员权限启动。

    显卡加速:

    python fishing.py image -f yolox/exp/yolox_tiny_fish.py -c weights/best_tiny3.pth --conf 0.25 --nms 0.45 --tsize 640 --device gpu

    cpu 运行:

    python fishing.py image -f yolox/exp/yolox_tiny_fish.py -c weights/best_tiny3.pth --conf 0.25 --nms 0.45 --tsize 640 --device cpu

    运行后出现 init ok 后按 r 键开始钓鱼,原神需要全屏。出于性能考虑检测框不会实时显示,处理运算后台进行。

    更多实现细节,读者可参考原项目。


    Go 业务开发中常用的几个开源库

    $
    0
    0

    前言

    哈喽,大家好,我是 asong。拖更了好久,这周开始更新。

    最近总有一些初学 Go语言的小伙伴问我在业务开发中一般都使用什么web框架、开源中间件;所以我总结了我在日常开发中使用到的库,这些库不一定是特别完美的,但是基本可以解决日常工作需求,接下来我们就来看一下。

    Gin

    Gin是一个用 Go编写的 Web框架,它是一个类似于 martini但拥有更好性能的 API框架。基本现在每个 Go初学者学习的第一个 web框架都是 Gin。在网上看到一个关于对各个Go-web框架受欢迎的对比:

    来自网络

    我们可以看到 Gin在社区受欢迎排第一,Gin 框架往往是进行 Web 应用开发的首选框架,许多公司都会选择采用 Gin框架进行二次开发,加入日志,服务发现等功能,像Bilibili 开源的一套 Go 微服务框架 Kratos 就采用 Gin 框架进行了二次开发。

    学习 Gin通过他的官方文档就可以很快入手,不过文档时英文的,这个不用担心,我曾翻译了一份中文版,可以到我的公众号后台获取,回复【gin】即可获取。

    github地址:https://github.com/gin-gonic/gin

    zap

    zapuber开源的日志库,选择 zap他有两个优势:

    • 它非常的快
    • 它同时提供了结构化日志记录和printf风格的日志记录

    大多数日志库基本都是基于反射的序列化和字符串格式化的,这样会导致在日志上占用大量 CPU资源,不适用于业务开发场景,业务对性能敏感还是挺高的。 zap采用了不同的方法,它设计了一个无反射、零分配的 JSON 编码器,并且基础 Logger 力求尽可能避免序列化开销和分配。通过在此基础上构建高级 SugaredLogger,zap 允许用户选择何时需要计算每次分配以及何时更喜欢更熟悉的松散类型的 API。

    zap的基准测试如下:

    来自官方文档

    可以看出 zap的效率完全高于其他日志库,选谁不用我明说了吧!!!

    github地址:https://github.com/uber-go/zap

    jsoniter

    做业务开发离不开 json的序列化与反序列化,标准库虽然提供了 encoding/json,但是它主要是通过反射来实现的,所以性能消耗比较大。 jsoniter可以解决这个痛点,其是一款快且灵活的 JSON 解析器,具有良好的性能并能100%兼容标准库,我们可以使用jsoniter替代encoding/json,官方文档称可以比标准库 快6倍多,后来Go官方在go1.12版本对 json.Unmarshal 函数使用 sync.Pool 缓存了 decoder,性能较之前的版本有所提升,所以现在达不到 快6倍多。

    来自官方文档

    github地址:https://github.com/json-iterator/go

    对于 jsoniter优化原理感兴趣的可以移步这里:http://jsoniter.com/benchmark.html#optimization-used

    gorm

    gorm是一个使用 Go语言编写的 ORM框架,文档齐全,对开发者友好,并且支持主流的数据库: MySQL, PostgreSQL, SQlite, SQL Server

    个人觉得使用 gorm最大的好处在于它是由国人开发,中文文档齐全,上手很快,目前大多数企业也都在使用 gorm。我们来一下 gorm的特性:

    • 全功能 ORM
    • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
    • Create,Save,Update,Delete,Find 中钩子方法
    • 支持 PreloadJoins的预加载
    • 事务,嵌套事务,Save Point,Rollback To Saved Point
    • Context、预编译模式、DryRun 模式
    • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
    • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
    • 复合主键,索引,约束
    • Auto Migration
    • 自定义 Logger
    • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
    • 每个特性都经过了测试的重重考验
    • 开发者友好

    github地址:https://github.com/go-gorm/gorm

    官方文档:https://gorm.io/zh_CN/docs/index.html

    robfig/cron

    github地址:https://github.com/robfig/cron

    业务开发更离不开定时器的使用了, cron就是一个用于管理定时任务的库,用 Go 实现 Linux 中 crontab这个命令的效果,与Linux 中 crontab命令相似, cron库支持用 5个空格分隔的域来表示时间。 cron上手也是非常容易的,看一个官方的例子:

    package main   

    import (
      "fmt"
      "time"

      "github.com/robfig/cron/v3"
    )

    func main() {
      c := cron.New()

      c.AddFunc("@every 1s", func() {
        fmt.Println("tick every 1 second run once")
      })
      c.Start()
      time.Sleep(time.Second * 10)
    }

    针对 cron的使用可以参考这篇文章:https://segmentfault.com/a/1190000023029219

    之前我也写了一篇 cron的基本使用,可以参考下:https://mp.weixin.qq.com/s/Z4B7Tn8ikFIkXVGhXNbsVA

    wire

    1202年了,应该不会有人不知道依赖注入的作用了吧。我们本身也可以自己实现依赖注入,但是这是在代码量少、结构不复杂的情况下,当结构之间的关系变得非常复杂的时候,这时候手动创建依赖,然后将他们组装起来就会变的异常繁琐,并且很容出错。Go语言社区有很多依赖注入的框架,可以分为两个类别:

    • 依赖反射实现的运行时依赖注入:inject、uber、dig
    • 使用代码生成实现的依赖注入:wire

    个人觉的使用 wire进行项目管理是最好的,在代码编译阶段就可以发现依赖注入的问题,在代码生成时即可报出来,不会拖到运行时才报,更便于 debug

    wire的使用也是非常的简单,关于 wire的使用我之前也写了一篇文章,可以参考一下:https://mp.weixin.qq.com/s/Z4B7Tn8ikFIkXVGhXNbsVA

    github地址:https://github.com/google/wire

    ants

    某些业务场景还会使用到 goroutine池, ants就是一个广泛使用的goroute池,可以有效控制协程数量,防止协程过多影响程序性能。 ants也是国人开发的,设计博文写的也很详细的,目前很多大厂也都在使用 ants,经历过线上业务检验的,所以可以放心使用。

    github地址:https://github.com/panjf2000/ants

    ants源码不到 1k行,建议大家赏析一下源码~。

    总结

    本文列举的几个库都是经常被使用的开源库,这几个库你都掌握了,基本的业务开发都没有啥问题了,一些初学者完全可以通过这几个库达到入门水平。还有一些库,比如: go-redisgo-sql-driverdidi/gendrygolang/groupcacheolivere/elastic/v7等等,这些库也是经常使用的,入门都比较简单,就不这里详细介绍了。

    如果大家也有经常使用的,比较好的开源库,欢迎推荐给我,我也学习学习!!!

    好啦,本文就到这里了,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!我是 asong,我们下期见。

    创建了读者交流群,欢迎各位大佬们踊跃入群,一起学习交流。入群方式:关注公众号获取。更多学习资料请到公众号领取。

    推荐往期文章:

    Viewing all 265 articles
    Browse latest View live
    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>