应用多环境部署和Redis高可用

一,应用多环境部署(spring boot提供了类似的方案)
    
1,前言
     常规的开发模式里面,应用的部署环境会分为DEV, SIT和PROD(分别表示开发、测试和生产环境,很多也有UAT环境,成熟的应用还有灰度版环境)。由于这些多样性的应用环境,给团队开发人员不仅引入了极大的不便,同时大大的增加了应用出错的概率。这是因为在JavaWeb的应用开发中,基本都会使用.properties作为最简便的参数配置文件。这也意味着多个应用环境,需要准备各自独立的配置参数,而如果这种根据不同的应用环境打包成不同的参数配置的应用包的操作都让开发人员手工完成。那么开发效率不说,用墨菲定律的话说,就是必出大娄子。因此针对这种情况,期望提供一个方案,可以做到如下两点:a,在应用包中提供针对不同应用环境的.properties文件,系统在运行时可以动态的根据当前环境选择相应的.properties文件;b,系统在启动时,会首先检查当前运行环境的配置,保证应用配置文件被正确加载。
 
2,动态加载.properties
     所谓动态加载应用相关的.properties配置文件,是指可以依据不同运行环境动态的选择合适的配置文件。值得一提的是,maven的<profiles/>是可以做到手动的根据不同的运行环境,选择install不同的包。但是maven的<profiles/>方式还是有诸多易错的地方:1),生成的war包,你无法区分它属于什么环境;2),配置参数写到了pom文件里,增加了管理上的麻烦(pom和properties文件职责混合);3),这种方式本质是在编译时,将响应的参数替换为实际的值,无法在运行时动态获取;4),编译完成的包只能在一个确定的环境下运行,不能做到一个包不做任何修改即可在任意环境下运行,这是很不方便的(常见如,将测试完成的准生产包直接迁移到生产环境;生产环境的包迁到测试环境测试等)。因此,更完善的动态.properties文件加载需要做如下三件事:
     a)在web应用容器中配置自定义JVM运行参数"-Ddeploy.env"为不同的应用环境设置不同的环境运行参数。例如SIT环境"-Ddeploy.env=sit"
     b)为不同的环境准备一份不同的.properties配置文件。如下图1所示,其中app.properties存放公共的配置参数。
 
图1
  c)spring的property-placeholder的配置写成如下形式:
<context:property-placeholder
     location="classpath:properties/app.properties,
     classpath:properties/app-${deploy.env}.properties"
                      ignore-resource-not-found="true" ignore-unresolvable="true" />

特别地,由于是运行时动态选择配置文件,所以需要将ignore-resource-not-found和ignore-unresolvable两项配置置为true,保证工程可以编译通过。这样的配置,有一个缺陷是,无法在编译时就能判断配置文件能否正确加载。因此,引入了前言部分提到的第二个工作项:运行参数的检查和初始化。

3,运行参数的检查和初始化
    如第2部分提到的,为了保证.properties文件在启动时可以加载,有必要在应用启动前首先检查JVM运行参数"-Ddeploy.env"是否配置。因此,需要定义一个实现ServletContextListener接口的监听类,并在让其在应用启动之初执行。该监听类定义如下:
PropertyConfigurationListener.java
/**
* 预设系统参数的监听器,在web容器创建{@link javax.servlet.ServletContext}时执行操作
* <p>该监听器配置在servlet应用的web.xml中,一般作为首个listener配置,务必放在
* {@link org.springframework.web.context.ContextLoaderListener}之前
* <p>通过读取环境变量中的deploy.env来生成环境标识,并存入到Java系统属性中
*/
public class PropertyConfigurationListener implements ServletContextListener {

    /** 预先设置的环境类型,DEV,SIT,PROD等 */
    public final static String DEPLOY_ENV_KEY        = "deploy.env";

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.resolveProfileValue();
    }

    private void resolveProfileValue() {
        String profileValue = "";
        if (SystemPropertyUtil.contains(DEPLOY_ENV_KEY)) {
            profileValue = SystemPropertyUtil.get(DEPLOY_ENV_KEY);
        } else {
            throw new IllegalStateException(
                    "获取不到'" + DEPLOY_ENV_KEY
                            + "'的值,检查是否在 '.profile'配置中配置或在JVM启动时加上参数'-D"
                            + DEPLOY_ENV_KEY + "'。");
        }
        SystemPropertyUtil.set(ACTIVE_PROFILES_PROPERTY_NAME, profileValue.toLowerCase());
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        //Do nothing!
    }
}

值得一提的是,在resolveProfileValue方法的最后一行,设置了AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME的值为当前环境的配置值。这样做的目的是,应用很可能在不同的运行环境下,会使用spring-context.xml中的针对不同环境的配置项(<beans profile="" />) 。最后为了让PropertyConfigurationListener在启动之初执行,将其配置在web.xml的最前面,配置如下

<listener>
  <listener-class>profile.PropertyConfigurationListener</listener-class>
</listener>
 
二,Redis高可用
     Redis当前版本已经很好的实现了集群服务,但是在大多数非大数据量和高并发场景下,只需要做到高可用即可满足业务需求。
 
1,Spring应用客户端配置
在Spring框架中,常见的是实例化RedisTemplate的配置。
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
  <property name="maxIdle" value="${redis.pool.maxIdle}" />
  <property name="timeBetweenEvictionRunsMillis" value="${redis.pool.timeBetweenEvictionRunsMillis}" />
  <property name="minEvictableIdleTimeMillis" value="${redis.pool.minEvictableIdleTimeMillis}" />
  <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
  <property name="maxTotal" value="${redis.pool.maxTotal}" />
  <property name="testWhileIdle" value="${redis.pool.testWhileIdle}" />
</bean>
<bean id="redisSentinelConfig" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
  <constructor-arg name="propertySource" >
      <bean class="org.springframework.core.env.MapPropertySource">
        <constructor-arg name="name" value="sentinelConfig" />
        <constructor-arg name="source">
            <map>
              <entry key="spring.redis.sentinel.master" value="${redis.master.name}" />
              <entry key="spring.redis.sentinel.nodes" value="${redis.sentinel.nodes}" />
            </map>
        </constructor-arg>
      </bean>
  </constructor-arg>
</bean>
<bean id="jedisConnectionFactory"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  <constructor-arg name="sentinelConfig" ref="redisSentinelConfig" />
  <constructor-arg name="poolConfig" ref="jedisPoolConfig" />
  <property name="password" value="${redis.password}" />
</bean>

<bean id="redisStringTemplate" class="org.springframework.data.redis.core.RedisTemplate"
  p:connection-factory-ref="jedisConnectionFactory" />
特别地,redis(sentinal)如果打开了安全模式,则客户端连接redis要么绑定IP,要么使用密码访问。
2,Redis服务端配置
  这里主要是两部分的配置Sentinal和Redis的主从配置。常规的配置选项在此不做特别说明,主要记录主要的配置项的意义。
  Redis的主从备份配置,除了和主机相同的配置外,所有从机都需要如下一项配置
slaveof 192.168.80.33 6379

该配置说明,该从机监听IP和端口分别为192.168.80.33和6379的主机服务,从机服务上线之后会将其服务信息注册到主机。

  所有的Sentinal都使用相同的配置,包含如下一项配置
sentinel monitor mymaster 192.168.80.33 6379 2

该配置说明,该Sentinel启动时首先会去监控IP和端口分别为192.168.80.33和6379的主机服务,2代表判断主节点服务失败至少需要2个节点。每一个Sentinal服务上线后,一方面会主动将自身的信息注册到主服务节点,另一方面也会动态地去获取主服务节点的已注册信息。因此,各个Sentinal节点可以感知到各种的存在,并且各个Sentinal节点都能获取到Redis主从服务的所有信息,并对主从服务节点进行监控。

原文地址:https://www.cnblogs.com/shenjixiaodao/p/8631826.html