作者归档:心远

关于心远

阿里巴巴中间件资深研发

博客迁移公告

鉴于访问workpress速度较慢,整站迁移到独立域名:http://heartaway.cn    heartaway.cn

Advertisements

spring quartz 指定机器运行trigger

我们在使用spring-quartz的时候,常常会遇到一个场景,那就是任务的调度会随着争取锁的先后顺序而出现不固定机器执行的场景,这在正常业务逻辑中具备了很好的容灾能力,但是在我们排查问题时,却带来了困绕,如果出现问题,我们期望任务调度固定在一台机器上进行执行,方便我们对问题的定位和排查。

这里就探讨如何扩展spring-quartz来实现任务的固定机器执行。

整体思路

要指定机器运行trigger,那首先我们必须清楚spring-quartz cluster模式下,任务的触发时如何进行分布式执行的。
spring-quartz cluster 是借助数据锁来实现并发控制的,需要注意的是分布式环境下需要保证各机器系统时间一致性;

核心处理线程QuartzSchedulerThread决定了扫描那些JOB,以及触发执行和生命周期的维护。
QuartzSchedulerThread中run方法:

//在trigger表中扫描指定SCHED_NAME、状态为WAITING,下次触发时间在30秒内的触发器
triggers = qsRsrcs.getJobStore().acquireNextTriggers(
 now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
.... 
//触发器执行
List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers); 
...
//释放触发器
qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
...
//完成触发器
qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);

我们就可以在acquireNextTriggers中做扩展,获取全部或者指定了实例ID的trigger。

延伸思考:acquireNextTriggers获取到trigger列表后,假设机器宕机,这些trigger如何路由到其他机器中正常运行?

 

扩展实例ID的生成策略

spring-quartz一般配置的org.quartz.scheduler.instanceId:AUTO,采用的是SimpleInstanceIdGenerator ID生成策略。

public class SimpleInstanceIdGenerator implements InstanceIdGenerator {
 public String generateInstanceId() throws SchedulerException {
 try {
 return InetAddress.getLocalHost().getHostName() + System.currentTimeMillis();
 } catch (Exception e) {
 throw new SchedulerException("Couldn't get host name!", e);
 }
 }
}

SimpleInstanceIdGenerator 采用机器主机名称与当前时间戳作为instanceId,我们期望在开发环境使用hostName,在生产环境(Linux)采用IP作为实例ID。

实现InstanceIdGenerator接口,实现自己的ID生成策略,QuartzSchedulerInstanceIdGenerator的实现逻辑为:

public class QuartzSchedulerInstanceIdGenerator implements InstanceIdGenerator {

private static final Logger log = LoggerFactory.getLogger(QuartzSchedulerInstanceIdGenerator.class);

private static final String OS_NAME = "os.name";

private static final String WINDOWS = "Windows";

private static final String MAC = "Mac OS";

@Override
 public String generateInstanceId() throws SchedulerException {
 String id;
 try {
 if (isLocalDev()) {
 id = getHostName();
 } else {
 id = IpUtil.getIp();
 }

if (StringUtils.isBlank(id)) {
 id = InetAddress.getLocalHost().getHostName() + System.currentTimeMillis();
 }
 } catch (Exception e) {
 throw new SchedulerException("Couldn't generate instance id!", e);
 }

return id;
 }

private String getHostName() throws SchedulerException {
 try {
 return InetAddress.getLocalHost().getHostName();
 } catch (Exception e) {
 throw new SchedulerException("Couldn't get host name!", e);
 }
 }

private boolean isLocalDev() {
 if (StringUtils.indexOfIgnoreCase(System.getProperty(OS_NAME), WINDOWS) >= 0) {
 return true;
 }

if (StringUtils.indexOfIgnoreCase(System.getProperty(OS_NAME), MAC) >= 0) {
 return true;
 }

return false;
 }
}

在quartz.properties中配置:

org.quartz.scheduler.instanceIdGenerator.class=com.xx.ext.QuartzSchedulerInstanceIdGenerator

扩展StdJDBCDelegate

SchedulerFactoryBean 工厂Bean负责加载配置信息,初始化SchedulerFactory和Scheduler实例,jobStore负责job、trigger等的持久化工作,针对不同的数据库类型,可以配置不同的DriverDelegate。Spring中默认使用LocalDataSourceJobStore作为JobStore的处理类。

SchedulerFactory中initSchedulerFactory方法:

 if (this.configLocation != null) { 
   if (this.configLocation != null) { 
     if (logger.isInfoEnabled()) { 
       logger.info("Loading Quartz config from [" + this.configLocation + "]"); 
     } 
     PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);  
 }
     CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
    if (this.dataSource != null) { 
      mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
    }

可以看到SchedulerFactoryBean中设置PROP_JOB_STORE_CLASS属性是在合并用户设置的配置文件之后,也就是PROP_JOB_STORE_CLASS的实现类Spring强制指定为LocalDataSourceJobStore而无法更改,即便是我们在quartz.properties中配置了org.quartz.jobStore.class属性,也会被LocalDataSourceJobStore覆盖掉。

LocalDataSourceJobStore继承自JobStoreSupport,JobStoreSupport默认配置使用StdJDBCDelegate作为与数据库交互的代理处理类;我们可以通过扩展StdJDBCDelegate类,来实现底层数据库交互的扩展。通过配置org.quartz.jobStore.driverDelegateClass属性,指定driverDelegate为我们扩展后的Delegate。

在quartz.properties中配置:

org.quartz.jobStore.driverDelegateClass=com.xx.ext.StdJDBCDelegateExt

针对${table_prefix}_quartz_triggers表新增字段INSTANCE_NAME,此字段为InstanceIdGenerator生成的实例ID,此字段会透传到StdJDBCDelegate中的属性instanceId中,因此我们可以扩展StdJDBCDelegate,通过instanceId字段来指定获取的trigger列表。

public class StdJDBCDelegateExt extends StdJDBCDelegate {

String INSTANCE_NAME_VALUE = "{2}";

String SELECT_NEXT_TRIGGER_TO_ACQUIRE_EXT = "SELECT "
 + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", "
 + COL_NEXT_FIRE_TIME + ", " + COL_PRIORITY + " FROM "
 + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE "
 + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST
 + " AND " + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " <= ? "
 + " AND (" + COL_INSTANCE_NAME + " IS NULL OR " + COL_INSTANCE_NAME + " = " + INSTANCE_NAME_VALUE + ")"
 + "AND (" + COL_MISFIRE_INSTRUCTION + " = -1 OR (" + COL_MISFIRE_INSTRUCTION + " != -1 AND "
 + COL_NEXT_FIRE_TIME + " >= ?)) "
 + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC";

/**
 * 允许设置trigger 指定一台机器进行任务调度执行;
 *
 * @param conn
 * @param noLaterThan
 * @param noEarlierThan
 * @param maxCount
 * @return
 * @throws SQLException
 */
 @Override
 public List<TriggerKey> selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan, int maxCount)
 throws SQLException {
 PreparedStatement ps = null;
 ResultSet rs = null;
 List<TriggerKey> nextTriggers = new LinkedList<TriggerKey>();
 try {
 ps = conn.prepareStatement(rtp(SELECT_NEXT_TRIGGER_TO_ACQUIRE_EXT, instanceId));

// Set max rows to retrieve
 if (maxCount < 1) {
 maxCount = 1;
 }
 ps.setMaxRows(maxCount);

// Try to give jdbc driver a hint to hopefully not pull over more than the few rows we actually need.
 // Note: in some jdbc drivers, such as MySQL, you must set maxRows before fetchSize, or you get exception!
 ps.setFetchSize(maxCount);

ps.setString(1, STATE_WAITING);
 ps.setBigDecimal(2, new BigDecimal(String.valueOf(noLaterThan)));
 ps.setBigDecimal(3, new BigDecimal(String.valueOf(noEarlierThan)));
 rs = ps.executeQuery();

while (rs.next() && nextTriggers.size() <= maxCount) {
 nextTriggers.add(triggerKey(
 rs.getString(COL_TRIGGER_NAME),
 rs.getString(COL_TRIGGER_GROUP)));
 }

return nextTriggers;
 } finally {
 closeResultSet(rs);
 closeStatement(ps);
 }
 }

protected final String rtp(String query, String instanceName) {
 return MessageFormat.format(query,
 new Object[] {tablePrefix, getSchedulerNameLiteral(), "'" + instanceName + "'"});
 }
}

至此,完成了指定trigger在特定instanceId上运行,但是有一个问题,假如我们期望trigger可以在多台instanceId上随机执行的话,该如何实现呢?

 

 

 

 

 

 

Spring中property-placeholder的使用与解析

在我们程序开发中,进程会需要把一些变量通过property方式进行提取,方便不同环境配置不同的属性,替换变量的方法通常有两种,一种是静态替换,一种是动态替换;所谓静态替换,是在打包编译的时候,把变量替换掉,动态替换,是在程序运行起来时,通过把属性注入到程序的环境变量中,类初始化的时候,再使用环境变量进行替换的一种方法。

静态替换常用工具:autoconfig
动态替换常用工具:spring.property-placeholder

spring动态替换变量实践

简洁配置法:

<context:property-placeholder location="classpath:xxx.properties" 
ignore-unresolvable="true"/>

等价于:

<bean id="propertyConfigurer" class=
"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 <property name="location">
 <value>myConfig.properties</value>
 </property>
 </bean>

完整配置属性:

<context:property-placeholder 
 location="" 
 file-encoding="" 
 ignore-resource-not-found="" 
 ignore-unresolvable="" 
 properties-ref="" 
 local-override="" 
 system-properties-mode="" 
 order="" 
/>

ignore-resource-not-found:如果属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常

ignore-unresolvable:是否忽略解析不到的属性,如果不忽略,找不到将抛出异常

order:当配置多个<context:property-placeholder/>时的查找顺序

不推荐将ignore-resource-not-found和ignore-unresolvable的值设置为ture,默认为false,可以有效避免程序运行异常。

使用PropertySource注解配置

Spring3.1添加了@PropertySource注解,方便添加property文件到环境.

properties的注入与使用

java中使用@Value注解获取:

@Value( "${jdbc.url}" )
private String jdbcUrl;

在Spring的xml配置文件中获取:

<bean id="dataSource">
 <property name="url" value="${jdbc.url}" />
</bean>

配置多个property-placeholder属性

Spring容器是采用反射扫描的发现机制,通过标签的命名空间实例化实例,当Spring探测到容器中有一个org.springframework.beans.factory.config.PropertyPlaceholderCVonfigurer的Bean就会停止对剩余PropertyPlaceholderConfigurer的扫描,即只能存在一个实例!

所以一遍不建议配置多个property-placeholder对象,但是在必须使用多个的场景下,如何配置呢?

<context:property-placeholder location="xxx.properties" ignore-unresolvable="true"/>
<context:property-placeholder location="xxx.properties" ignore-unresolvable="true"/>

需要设置ignore-unresolvable=”true”,否则后面的property-placeholder不会被加载;ignore-unresolvable单独使用来看是“是否忽视不存在的配置项”,不仅如此,其还有一个隐含意思:是否还要扫描其他配置项:如果为false,则会忽视后续的property-placeholder,如果需要配置多个property-placeholder则应该设置为true;

context:property-placeholder 工作原理

在 ContextNamespaceHandler 中对于 context中的property-placeholder 标签,会采用PropertyPlaceholderBeanDefinitionParser解析器进行解析;

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
 @Override public void init() { 
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); 
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); 
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); 
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); 
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); 
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); 
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); 
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); 
  }
}

PropertyPlaceholderBeanDefinitionParser解析器会将property-placeholder 标签解析为一个PropertySourcesPlaceholderConfigurer的单例 bean 。

可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer 仅仅是做了一个配置文件的解析工作,真正的注入并不由它们完成,而是托付给了Spring 的Bean初始化流程。
之所以这么做可以生效,是因为这两个类实现了 BeanFactoryPostProcessor 接口,这个接口的优先级高于后续的Spring Bean。

属性元素的注入依赖于 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues。 通过解析PropertySourcesPlaceholderConfigurer 查询得到元素值。

public PropertyValues postProcessPropertyValues(PropertyValues pvs, 
PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
 InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass());
    try {
        metadata.inject(bean, beanName, pvs);
        return pvs;
    } catch (Throwable var7) {
        throw new BeanCreationException(beanName,
        "Injection of autowired dependencies failed", var7);
    }
 }

PropertySourcesPlaceholderConfigurer本质上是一个BeanFactoryPostProcessor。解析XML的流程在BeanFactoryPostProcessor之前, 优先将配置文件的路径以及名字通过Setter传入PropertySourcesPlaceholderConfigurer。

如上BeanFactoryPostProcessor的优先级又优于其余的Bean。因此可以实现在bean初始化之前的注入。

Spring @Value注入流程

  1. Spring Context 的初始化开始
  2. 读取到context:property-placeholder标签或者PropertySourcesPlaceholderConfigurer
  3. 解析并实例化一个PropertySourcesPlaceholderConfigurer。同时向其中注入配置文件路径、名称
  4. PropertySourcesPlaceholderConfigurer自身生成多个StringValueResolver备用,Bean准备完毕
  5. Spring在初始化非BeanFactoryPostProcessor的Bean的时候,AutowiredAnnotationBeanPostProcessor 负责找到Bean内有@Value注解的Field或者Method
  6. 通过PropertySourcesPlaceholderConfigurer寻找合适的StringValueResolver并解析得到val值。注入给@Value的Field或Method。(Method优先)2
  7. Spring的其他流程。

 

参考:

http://blog.csdn.net/qyp199312/article/details/54313784

Molly 安装与测试

Molly下载地址:https://github.com/palvaro/molly

首先阅读tutorial.md文件,安装方法按照Installation.md来执行;

需要注意点:

  1. 在mac OS下不能使用brew install来安装apr、apr-util,需要下载这两个组件的源码,通过make、install的方式进行安装;
  2. bin 目录下只有c4,缺少z3的安装文件,需要自己下载安装;
  3. 依赖安装完毕后,需要将依赖的库添加到PATH中;

安装Scala构建工具:sbt

brew install sbt@1

参考:http://www.scala-sbt.org/download.html

继续阅读

如何提高微服务架构的可用性

业界通常用多少个9来衡量系统的可用性,如99.99%表示一年中有1小时左右的不可用时间。任何一个服务的可用性都不会是100%,意味着在服务运行时间里还是有可能发生故障。当把功能集中且运行在同一个应用中的单体架构拆分成多个相互独立的微服务架构后,虽然可以降低一损俱损的全局性故障风险,但由于微服务之间存在大量的依赖关系, 随着微服务个数的增多,依赖关系也将会变得越来越复杂,而且每个微服务都有可能发生故障,如果不能做好相互依赖的隔离,避免故障的连锁反应,结果可能比单体更糟糕。

假设有100个微服务,并且每个微服务只会发生1种故障,那么总共会有 2100 种不同的故障场景,而每个微服务自身可能不止1种故障。当某个微服务发生故障时,如何确保不会导致其他依赖的微服务不可用, 如何确保系统自动降级把发生故障的微服务排除出去,如何确保故障不会扩展到整个系统? 那么如何有效确保微服务架构的可用性将会成为挑战。 继续阅读

Blue-green Deployments, A/B Testing, and Canary Releases

A lot of teams I talk to recently are very interested in “DevOps” (whatever that means… seems to mean different things to different people?) and when we sit down and talk about what that really means, the direction of the conversation can go down many interesting paths. And some times, the path it goes down makes people feel very uncomfortable. I was talking with a team a while back about deployment best practices, hot deployments, rollbacks etc and when I mentioned blue-green deployments, they became a bit queasy. Another team couldn’t understand why doing something they’ve always done was not such a very good thing.

blue-green deployments have been practiced at places like Amazon for 10+ years. They’re a safe, proven, method. Now, blue-green deployments are not a silver bullet, but there’s an element of usefulness to them. But what about A/B testing then? Or even Canary testing? With all of the #microservices, DevOps, and cloud-native talk, there’s a lot of discussion about them, but I wanted to clarify their differences. 继续阅读

The Twelve-Factor App

简介

如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS)。12-Factor 为构建如下的 SaaS 应用提供了方法论:

  • 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
  • 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。
  • 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
  • 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
  • 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。

这套理论适用于任意语言和后端服务(数据库、消息队列、缓存等)开发的应用程序。

继续阅读