Skip to content

Quick_Start

睿驰 edited this page Mar 16, 2021 · 12 revisions

首先,添加juice-spring-boot-starter依赖:

<dependency>
    <groupId>io.infinityclub</groupId>
    <artifactId>juice-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

示例代码仓库: juice-samples

1、分布式锁

application.yml增加redis配置:

# redis
spring:
  redis:
    database: 0
    host: localhost
    password: admin
    port: 6379
    ssl: false
    lettuce:
      pool:
        max-wait: 5000ms
        maxActive: 20
        maxIdle: 8
        minIdle: 1
    timeout: 3000ms

代码示例:

    @Resource
    private DistributedLockClient distributedLockClient;

    public ResponseDTO generateBill(BillGenerationMgtParamDTO paramDTO) {
        Date now = new Date();
        Long loginUserId = paramDTO.getUserId();
        //全局加锁
        String lockKey = ConsumerBillConstant.getGenerateBillLockKey(loginUserId);
        DistributedLock lock = distributedLockClient.getLock(lockKey);
        boolean lockSuccess = lock.tryLock(-1, 5, TimeUnit.MINUTES);
        if (!lockSuccess) {
            LOG.info("订单管理-生成账单, loginUserId:{} 加锁失败:{}", loginUserId, lockKey);
            return ResponseDTO.invalidParam("加锁失败");
        }

        try {
            //业务逻辑代码

        } finally {
            lock.unlock();
        }
        return ResponseDTO.systemError();
    }

2、限流器

/**
 * @author Ricky Fung
 */
public class AppTest {

    private RateLimiter semaphoreBasedRateLimiter;
    private AtomicRateLimiter atomicRateLimiter;
    private RedisRateLimiter redisRateLimiter;

    @Before
    public void setUp() {
        RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom()
                .limitForPeriod(20)
                .limitRefreshPeriod(Duration.ofMillis(1000))
                .timeoutDuration(Duration.ofSeconds(3))
                .build();
        semaphoreBasedRateLimiter = new SemaphoreBasedRateLimiter("semaphoreBased",
                rateLimiterConfig);
        atomicRateLimiter = new AtomicRateLimiter("atomicBased", rateLimiterConfig);
        redisRateLimiter = new RedisRateLimiter("", rateLimiterConfig);
    }
}

在业务代码调用之前:

    public ResultDTO secKill() {
        boolean success = redisRateLimiter.tryAcquire();
        if (!success) {
            return ResultDTO.invalidParam("业务繁忙");
        }
        //正常业务处理
        ......

        return ResultDTO.ok();
    }

3、分布式链路追踪

application.yml增加如下配置:

# tracing
juice.tracing.enable = true
juice.tracing.pathPatterns = /api/*

应用启动后,juice-spring-boot-starter会自动注入juice.tracing.filter.TracingFilter,接口响应头会出现 X-TraceId

如果希望RestTemplate发送请求时自动携带traceId和spanId,可以在RestTemplate上加上@DistributedTracing,如下:

import juice.tracing.annotation.DistributedTracing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @DistributedTracing
    @Bean
    public RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(30000);//单位为ms
        factory.setConnectTimeout(20000);//单位为ms

        return new RestTemplate(factory);
    }

}

4、动态数据源切换

application.yml增加如下配置:

# DB
# 主库配置.eweishop
spring.datasource.eweishop.master.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.eweishop.master.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.eweishop.master.name = eweishop_master
spring.datasource.eweishop.master.url = jdbc:mysql://localhost:3306/eweishop_ms?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
spring.datasource.eweishop.master.username = root
spring.datasource.eweishop.master.password = root@2021
spring.datasource.eweishop.master.filters = mergeStat
spring.datasource.eweishop.master.initial-size = 5
spring.datasource.eweishop.master.max-active = 100
spring.datasource.eweishop.master.min-idle = 1
spring.datasource.eweishop.master.max-wait = 20000
spring.datasource.eweishop.master.test-on-borrow = false
spring.datasource.eweishop.master.test-on-return = false
spring.datasource.eweishop.master.test-while-idle = true
spring.datasource.eweishop.master.validation-query = SELECT 'x'
spring.datasource.eweishop.master.time-between-eviction-runs-millis = 60000
spring.datasource.eweishop.master.min-evictable-idle-time-millis = 300000

# 从库配置.eweishop
spring.datasource.eweishop.slave.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.eweishop.slave.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.eweishop.slave.name = eweishop_slave
spring.datasource.eweishop.slave.url = jdbc:mysql://localhost:3306/eweishop_slave?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
spring.datasource.eweishop.slave.username = root
spring.datasource.eweishop.slave.password = root@2021
spring.datasource.eweishop.slave.filters = mergeStat
spring.datasource.eweishop.slave.initial-size = 5
spring.datasource.eweishop.slave.max-active = 100
spring.datasource.eweishop.slave.min-idle = 1
spring.datasource.eweishop.slave.max-wait = 20000
spring.datasource.eweishop.slave.test-on-borrow = false
spring.datasource.eweishop.slave.test-on-return = false
spring.datasource.eweishop.slave.test-while-idle = true
spring.datasource.eweishop.slave.validation-query = SELECT 'x'
spring.datasource.eweishop.slave.time-between-eviction-runs-millis = 60000
spring.datasource.eweishop.slave.min-evictable-idle-time-millis = 300000

# 主库配置 bypass
spring.datasource.bypass.master.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.bypass.master.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.bypass.master.name = bypass_master
spring.datasource.bypass.master.url = jdbc:mysql://localhost/bypass_ms?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
spring.datasource.bypass.master.username = root
spring.datasource.bypass.master.password = root@2021
spring.datasource.bypass.master.filters = mergeStat
spring.datasource.bypass.master.initial-size = 5
spring.datasource.bypass.master.max-active = 100
spring.datasource.bypass.master.min-idle = 1
spring.datasource.bypass.master.max-wait = 20000
spring.datasource.bypass.master.test-on-borrow = false
spring.datasource.bypass.master.test-on-return = false
spring.datasource.bypass.master.test-while-idle = true
spring.datasource.bypass.master.validation-query = SELECT 'x'
spring.datasource.bypass.master.time-between-eviction-runs-millis = 60000
spring.datasource.bypass.master.min-evictable-idle-time-millis = 300000

# 从库配置 bypass
spring.datasource.bypass.slave.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.bypass.slave.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.bypass.slave.name = bypass_slave
spring.datasource.bypass.slave.url = jdbc:mysql://localhost:3306/bypass_slave?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
spring.datasource.bypass.slave.username = root
spring.datasource.bypass.slave.password = root@2021
spring.datasource.bypass.slave.filters = mergeStat
spring.datasource.bypass.slave.initial-size = 5
spring.datasource.bypass.slave.max-active = 100
spring.datasource.bypass.slave.min-idle = 1
spring.datasource.bypass.slave.max-wait = 20000
spring.datasource.bypass.slave.test-on-borrow = false
spring.datasource.bypass.slave.test-on-return = false
spring.datasource.bypass.slave.test-while-idle = true
spring.datasource.bypass.slave.validation-query = SELECT 'x'
spring.datasource.bypass.slave.time-between-eviction-runs-millis = 60000
spring.datasource.bypass.slave.min-evictable-idle-time-millis = 300000

# mybatis的相关配置
mybatis.mapper-locations = classpath*:mapper/**/*Mapper.xml
mybatis.type-aliases-package = com.renzhenmall.oms.storage.entity
mybatis.configLocation = classpath:mybatis/mybatis-config.xml

数据源配置类:

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.renzhenmall.oms.datasource.DataSourceKey;
import juice.datasource.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Ricky Fung
 */
@Configuration
@MapperScan(value = "com.renzhenmall.oms.storage.**.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DruidDataSourceConfig {

    @Value("${mybatis.mapper-locations:}")
    private String mapperLocations;

    @Value("${mybatis.type-aliases-package:}")
    private String typeAliasesPackage;

    @Value("${mybatis.configLocation:}")
    private String configLocation;

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        bean.setTypeAliasesPackage(typeAliasesPackage);
        bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(configLocation));
        return bean.getObject();
    }

    //===========
    @Bean
    public DynamicDataSource dynamicDataSource() {
        Map<String,String> pkgDefaultDsKeyMap = new HashMap<>(4);
        pkgDefaultDsKeyMap.put("com.renzhenmall.oms.storage.mapper.eweishop", DataSourceKey.MASTER_EWEISHOP);
        pkgDefaultDsKeyMap.put("com.renzhenmall.oms.storage.mapper.bypass", DataSourceKey.MASTER_BYPASS);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> tarDsMap = new HashMap<>(8);
        tarDsMap.put(DataSourceKey.MASTER_EWEISHOP, eweishopMasterDS());
        tarDsMap.put(DataSourceKey.SLAVE_EWEISHOP, eweishopSlaveDS());
        tarDsMap.put(DataSourceKey.MASTER_BYPASS, bypassMasterDS());
        tarDsMap.put(DataSourceKey.SLAVE_BYPASS, bypassSlaveDS());
        dynamicDataSource.setTargetDataSources(tarDsMap);

        dynamicDataSource.setDefaultTargetDataSource(tarDsMap.get(DataSourceKey.MASTER_EWEISHOP));

        dynamicDataSource.setPkgDefaultDsKeyMap(pkgDefaultDsKeyMap);

        return dynamicDataSource;
    }

    //==========
    @Bean
    @ConfigurationProperties("spring.datasource.eweishop.master")
    public DataSource eweishopMasterDS(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.eweishop.slave")
    public DataSource eweishopSlaveDS(){
        return DruidDataSourceBuilder.create().build();
    }

    //==========
    @Bean
    @ConfigurationProperties("spring.datasource.bypass.master")
    public DataSource bypassMasterDS(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.bypass.slave")
    public DataSource bypassSlaveDS(){
        return DruidDataSourceBuilder.create().build();
    }

}

在代码中动态切换数据源:

@Component
public class CustomerBillManager {

    @DSRouting(value = DataSourceKey.MASTER_BYPASS)
    @Transactional
    public int deleteBill(Long billId) {
        int rows = customerOrderBillMapper.updateDelete(billId);
        int detailRows = customerOrderBillDetailMapper.updateDelete(billId);
        //删除标记
        int goodsRows = customerOrderBillGoodsMapper.deleteByBillId(billId);
        LOG.error("甲方账单-删除账单, billId:{} 删除账单数量:{}, 账单详情数量:{}, 订单商品数量:{}", billId, rows, detailRows, goodsRows);
        return rows;
    }
}

CustomerOrderBillMapper如下:

@DSRouting(value = DataSourceKey.SLAVE_BYPASS)
public interface CustomerOrderBillMapper {
    int deleteByPrimaryKey(Long id);

    int insert(CustomerOrderBill record);

    int insertSelective(CustomerOrderBill record);

    CustomerOrderBill selectByPrimaryKey(Long id);

    List<CustomerOrderBill> selectByCondition(CustomerOrderBillCondition condition);

    int updateByPrimaryKeySelective(CustomerOrderBill record);

    int updateByPrimaryKey(CustomerOrderBill record);

    List<CustomerOrderBill> queryBillList(CustomerBillQueryParam query);

    int updateRemark(@Param("id") Long id, @Param("remark") String remark);

    int updateSettlementState(CustomerOrderBillUpdateCondition condition);

    int updateDelete(@Param("billId") Long billId);

    int updateSendMail(CustomerOrderBillUpdateCondition condition);

    int updateConfirm(CustomerOrderBillUpdateCondition condition);

}

Clone this wiki locally