apache shenyu 打包命令集

打包常用的命令 # example打包 ./mvnw -B clean install -Pexample -DskipTests -f ./shenyu-examples/shenyu-examples-springcloud/pom.xml ./mvnw -B clean install -Pexample -DskipTests -f ./shenyu-examples/shenyu-examples-dubbo/pom.xml ./mvnw -B clean install -Pexample -DskipTests -f ./shenyu-examples/shenyu-examples-http/pom.xml ./mvnw -B clean install -DskipTests -f ./shenyu-client/pom.xml ./mvnw -B clean install -Pexample -DskipTests -f ./shenyu-examples/pom.xml ./mvnw -B clean install -Prelease -DskipTests -f ./shenyu-dist/pom.xml ./mvnw -B clean install -Pdocker -DskipTests -f ./shenyu-dist/pom.xml # 集成测试打包 ./mvnw -B clean install -Pit -DskipTests -f ./shenyu-integrated-test/shenyu-integrated-test-spring-cloud/pom.xml ./mvnw -B clean install -Pit -DskipTests -f ./shenyu-integrated-test/shenyu-integrated-test-alibaba-dubbo/pom.xml ./mvnw -B clean install -Pit -DskipTests -f ./shenyu-integrated-test/shenyu-integrated-test-common/pom.xml ./mvnw -B clean install -Pit -DskipTests -f ./shenyu-integrated-test/pom.xml # docker-compose启动 docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-spring-cloud/docker-compose.yml up -d docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-spring-cloud/docker-compose.yml up -d docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-http/docker-compose.yml up -d # 执行测试 ./mvnw test -Pit -f ./shenyu-integrated-test/shenyu-integrated-test-spring-cloud/pom.xml ./mvnw test -Pit -f ./shenyu-integrated-test/shenyu-integrated-test-alibaba-dubbo/pom.xml ./mvnw test -Pit -f ./shenyu-integrated-test/shenyu-integrated-test-http/pom.xml ./mvnw test -Pit -f ./shenyu-integrated-test/shenyu-integrated-test-http/pom.xml # docker-compose停止 docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-spring-cloud/docker-compose.yml down docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-alibaba-dubbo/docker-compose.yml down docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-http/docker-compose.yml down registry.cn-hangzhou.aliyuncs.com/apache/shenyu-admin:latest docker-compose -f ./shenyu-integrated-test/shenyu-integrated-test-http/docker-compose.yml logs --tail="all" 本地编译报错 ./mvnw clean install -Dmaven.javadoc.skip=true 执行以上命令,本地编译报错。

非正规集成+压力测试

非正规集成测试和性能测试 6月份公司最重要的续费活动就要开始了,期间功能方面的改动不是特别大。但需要做性能测试和全部数据方面的验证。 基于以上(全部数据的验证、性能测试)目的,于是需要对接口进行性能测试。 1、工具调研 在工具调研方面,市面上常见的性能测试工具有以下。 JMeter Apache Bench TestNG LoadRunner 基于熟悉上手和对公司业务数据的了解程度,最后选择了TestNG。原因有: 编码方便,灵活 数据驱动方式还不错 灵活的设置线程数 基于TestNG可以很灵活的设计一样宽表来保存结果集。 最后设计的表结构如下 create table renewalintegrationtesting.t_2023_renewal_list ( id bigint auto_increment primary key, student_num varchar(128) null, student_id varchar(36) null, phone varchar(255) null, customer_id varchar(500) null, app_token varchar(255) null, authorization varchar(255) null, step1_url varchar(500) null comment '步骤一的接口地址', step1_request text null, step1_response text null, step1_success tinyint default 0 not null comment '步骤一执行结果0-不成功1-成功', step2_url varchar(500) null comment '步骤二的接口地址', step2_request text null, step2_response text null, step2_success tinyint default 0 not null comment '步骤二执行结果0-不成功1-成功', step3_url varchar(500) null comment '步骤3的接口地址', step3_request text null, step3_response text null, step3_success tinyint default 0 not null comment '步骤3执行结果0-不成功1-成功', step4_url varchar(500) null comment '步骤4的接口地址', step4_request text null, step4_response text null, step4_success tinyint default 0 not null comment '步骤4执行结果0-不成功1-成功', step5_url varchar(500) null comment '步骤5的接口地址', step5_request text null, step5_response text null, step5_success tinyint default 0 not null comment '步骤5执行结果0-不成功1-成功', step6_url varchar(500) null comment '步骤6的接口地址', step6_request text null, step6_response text null, step6_success tinyint default 0 not null comment '步骤6执行结果0-不成功1-成功', step7_url varchar(500) null comment '步骤7的接口地址', step7_request text null, step7_response text null, step7_success tinyint default 0 not null comment '步骤7执行结果0-不成功1-成功', step8_url varchar(500) null comment '步骤8的接口地址', step8_request text null, step8_response text null, step8_success tinyint default 0 not null comment '步骤8执行结果0-不成功1-成功', step9_url varchar(500) null comment '步骤9的接口地址', step9_request text null, step9_response text null, step9_success tinyint default 0 not null comment '步骤9执行结果0-不成功1-成功', step10_url varchar(500) null comment '步骤10的接口地址', step10_request text null, step10_response text null, step10_success tinyint default 0 not null comment '步骤10执行结果0-不成功1-成功' ); 2、测试用例的编写 示例测试用例代码如查看订单列表

apache shenyu重构之AbstractShenyuPlugin

重构AbstractShenyuPlugin.execute()方法 原来的执行流程 initMatchCacheConfig(); String pluginName = named(); PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName); if (Objects.nonNull(pluginData) && pluginData.getEnabled()) { final String path = exchange.getRequest().getURI().getPath(); SelectorData selectorData = obtainSelectorDataCacheIfEnabled(exchange); if (Objects.isNull(selectorData)) { List<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName); if (CollectionUtils.isEmpty(selectors)) { return handleSelectorIfNull(pluginName, exchange, chain); } Pair<Boolean, SelectorData> matchSelectorData = matchSelector(exchange, selectors); selectorData = matchSelectorData.getRight(); if (Objects.isNull(selectorData)) { if (matchCacheConfig.getSelectorEnabled() && matchSelectorData.getLeft()) { selectorData = new SelectorData(); selectorData.setPluginName(named()); cacheSelectorData(path, selectorData); } return handleSelectorIfNull(pluginName, exchange, chain); } else { if (matchCacheConfig.getSelectorEnabled() && matchSelectorData.getLeft()) { cacheSelectorData(path, selectorData); } } } else { if (StringUtils.isBlank(selectorData.getId())) { return handleSelectorIfNull(pluginName, exchange, chain); } } selectorLog(selectorData, pluginName); if (Objects.nonNull(selectorData.getContinued()) && !selectorData.getContinued()) { // if continued, not match rules return doExecute(exchange, chain, selectorData, defaultRuleData(selectorData)); } // handle rule RuleData ruleData = obtainRuleDataCacheIfEnabled(exchange); if (Objects.isNull(ruleData)) { // execute no cache List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId()); if (CollectionUtils.isEmpty(rules)) { return handleRuleIfNull(pluginName, exchange, chain); } if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) { //get last RuleData rule = rules.get(rules.size() - 1); ruleLog(rule, pluginName); return doExecute(exchange, chain, selectorData, rule); } else { Pair<Boolean, RuleData> matchRuleData = matchRule(exchange, rules); ruleData = matchRuleData.getRight(); if (Objects.isNull(ruleData)) { this.cacheRuleData(pluginName, path, matchRuleData); return handleRuleIfNull(pluginName, exchange, chain); } else { // if match success, cache rule. if (matchCacheConfig.getRuleEnabled() && matchRuleData.getLeft()) { cacheRuleData(path, ruleData); } } } } else { if (Objects.isNull(ruleData.getId())) { // handle rule not match return handleRuleIfNull(pluginName, exchange, chain); } } ruleLog(ruleData, pluginName); return doExecute(exchange, chain, selectorData, ruleData); } return chain.execute(exchange); 重构流程 获取变量数据 plugin selector rule 卫语句提前结束 processSelector 处理select为空的情况,卫语句提前执行 记录日志,不为空记录日志 已知selecor对象 缓存获取 不需要缓存对象 执行select选中逻辑 processRule 处理rule为空的情况,卫语句提前执行 记录日志,不为空记录日志 已知rule对象 缓存获取 不需要缓存对象 执行select选中逻辑 重构后的代码 initMatchCacheConfig(); final String pluginName = named(); final String path = exchange.getRequest().getURI().getPath(); PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName); List<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName); SelectorData selectorData = obtainSelectorDataCacheIfEnabled(path); // early exit if (Objects.isNull(pluginData) || !pluginData.getEnabled()) { return chain.execute(exchange); } // handle Selector if (Objects.nonNull(selectorData) && StringUtils.isBlank(selectorData.getId())) { return handleSelectorIfNull(pluginName, exchange, chain); } if (Objects.isNull(selectorData)) { if (CollectionUtils.isEmpty(selectors)) { return handleSelectorIfNull(pluginName, exchange, chain); } Pair<Boolean, SelectorData> matchSelectorData = matchSelector(exchange, selectors); selectorData = matchSelectorData.getRight(); if (Objects.isNull(selectorData)) { if (matchCacheConfig.getSelectorEnabled() && matchSelectorData.getLeft()) { selectorData = new SelectorData(); selectorData.setPluginName(pluginName); cacheSelectorData(path, selectorData); } return handleSelectorIfNull(pluginName, exchange, chain); } else { if (matchCacheConfig.getSelectorEnabled() && matchSelectorData.getLeft()) { cacheSelectorData(path, selectorData); } } } printLog(selectorData, pluginName); if (Objects.nonNull(selectorData.getContinued()) && !selectorData.getContinued()) { // if continued, not match rules return doExecute(exchange, chain, selectorData, defaultRuleData(selectorData)); } // handle Rule RuleData ruleData = obtainRuleDataCacheIfEnabled(path); List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId()); if (Objects.nonNull(ruleData) && Objects.isNull(ruleData.getId())) { return handleRuleIfNull(pluginName, exchange, chain); } if (CollectionUtils.isEmpty(rules)) { return handleRuleIfNull(pluginName, exchange, chain); } if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) { //get last RuleData rule = rules.get(rules.size() - 1); printLog(rule, pluginName); return doExecute(exchange, chain, selectorData, rule); } else { Pair<Boolean, RuleData> matchRuleData = matchRule(exchange, rules); ruleData = matchRuleData.getRight(); if (Objects.isNull(ruleData)) { this.cacheRuleData(pluginName, path, matchRuleData); return handleRuleIfNull(pluginName, exchange, chain); } else { // if match success, cache rule. if (matchCacheConfig.getRuleEnabled() && matchRuleData.getLeft()) { cacheRuleData(path, ruleData); } } } printLog(ruleData, pluginName); return doExecute(exchange, chain, selectorData, ruleData);

apache shenyu 介绍

shenyu是什么 是一个java实现的网关、高性能、跨语言的、响应式API网关。 架构图 模块说明 admin 管理后台和配置模块 bootstrap 程序的主入口 client 客户端模块,提供支持Spring MVC 、Dubbo 、Spring Cloud 快速访问 disruptor 基于disruptor的封装 register-center shenyu-client提供各种rpc接入注册中心的支持 common 框架的通用类 dist 构建项目 metrics prometheus(普罗米修斯)实现的 metrics plugin 插件集合 spin 定义的spi spring-boot-starter 支持 spring-boot-starter sync-data-center 提供 ZooKeeper,HTTP,WebSocket,Nacos 的方式同步数据 examples 示例模块 web 包括插件、请求路由和转发等核心处理包

redis使用分布式锁记

使用redis分布式锁记 背景介绍 在项目中使用封装的redis锁注解中,会出现异常信息。抛出拿分布式锁异常。 跟源码 其中lock对象为false的情况下会抛出Get redis lock failed的业务异常 @Around("redisLockPoint() && @annotation(redisLock)") public Object around(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable { String key = redisLock.key(); if (StringUtils.isBlank(key)) { Object[] args = pjp.getArgs(); if (redisLock.bindType().equals(RedisLock.BindType.DEFAULT)) { key = StringUtils.join(args); } else if (redisLock.bindType().equals(RedisLock.BindType.ARGS_INDEX)) { key = getArgsKey(redisLock, args); } else if (redisLock.bindType().equals(RedisLock.BindType.OBJECT_PROPERTIES)) { key = getObjectPropertiesKey(redisLock, args); } } Assert.hasText(key, "key does not exist"); String prefix = redisLock.prefix()+"_"; boolean lock = distributedRedisLock.lock(prefix + key, redisLock.expire(), redisLock.retryTimes(), redisLock.retryInterval()); if (!lock) { log.warn("get lock failed : " + key); if (redisLock.errorStrategy().equals(RedisLock.ErrorStrategy.THROW_EXCEPTION)) { throw new RedisException("Get redis lock failed"); } return null; } log.info("get lock success : {}", key); try { return pjp.proceed(); } finally { boolean result = distributedRedisLock.unLock(prefix + key); log.info("release lock : {} {}", prefix + key, result ? " success" : " failed"); } } 加锁的方法,通过redisTemplate执行execute方法,然后其中的SetOption参数为SET_IF_ABSENT /** * 加锁 * @param key 锁key * @param expire 过期时间 * @param retryTimes 重试次数 * @param retryInterval 重试间隔 * @return true 加锁成功, false 加锁失败 */ public boolean lock(String key, long expire, int retryTimes, long retryInterval) { boolean result = setRedisLock(key, expire); /** * 如果获取锁失败,进行重试 */ while((!result) && retryTimes-- > 0){ try { log.info("lock failed, retrying..." + retryTimes); Thread.sleep(retryInterval); } catch (InterruptedException e) { return false; } result = setRedisLock(key, expire); } return result; } /** * 设置redis锁 * @param key 锁key * @param expire 过期时间 * @return true 设置成功,false 设置失败 */ private boolean setRedisLock(String key, long expire) { try { RedisCallback<Boolean> callback = (connection) -> { String uuid = UUID.randomUUID().toString(); lockKey.set(uuid); return connection.set(key.getBytes(), uuid.getBytes(), Expiration.milliseconds(expire), RedisStringCommands.SetOption.SET_IF_ABSENT); }; return redisTemplate.execute(callback); }catch (Exception e){ log.error("set redis error", e); } return false; } 其中connection.set方法的释意如下,参数为 key,value,过期时间,和option /** * Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values * depending on {@code option}. * * @param key must not be {@literal null}. * @param value must not be {@literal null}. * @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} to not set any ttl. * @param option must not be {@literal null}. Use {@link SetOption#upsert()} to add non existing. * @return {@literal null} when used in pipeline / transaction. * @since 1.7 * @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a> */ @Nullable Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option); 其中option的值如下所示例 当前程序使用的是SET_IF_ABSENT 等同于redis的SETNX命令 /** * {@code SET} command arguments for {@code NX}, {@code XX}. * * @author Christoph Strobl * @since 1.7 */ enum SetOption { /** * Do not set any additional command argument. * * @return */ UPSERT, /** * {@code NX} * * @return */ SET_IF_ABSENT, /** * {@code XX} * * @return */ SET_IF_PRESENT; /** * Do not set any additional command argument. * * @return */ public static SetOption upsert() { return UPSERT; } /** * {@code XX} * * @return */ public static SetOption ifPresent() { return SET_IF_PRESENT; } /** * {@code NX} * * @return */ public static SetOption ifAbsent() { return SET_IF_ABSENT; } } 大胆假设,小心求证 观点一: redis写会失败 利用多线程模拟写操作 本地模拟10万个线程同时写操作,并未复现 写操作不包含重复的key 业务代码中 观点二: 写重复key导致失败 利用多线程模拟写操作 短时间内重复key 业务代码中有满足的条件 创建订单:key为customerId,表中存在customerId重复 发放优惠券:key为activeId,活动为key,也存在重复 观点一代码,多次模拟,未出现拿锁异常的情况 @PostMapping("/testRedisLock") public Result testRedisLock(@RequestParam Integer param) { final boolean[] flag = {false}; for(int i = 0 ;i<param;i++){ //方式1:相当于继承了Thread类,作为子类重写run()实现 int finalI = i; new Thread() { @Override public void run() { try{ payV2Service.testRedisLock(finalI); }catch (Exception e){ e.printStackTrace(); flag[0] = true; throw new RuntimeException("失败:"); } }; }.start(); } return Result.succeed(flag[0],"成功"); } @Override @RedisLock(prefix = "CREATORDER:TEST") public Object testRedisLock(Integer param) { log.info("加锁成功:" + param); return true; } 观点二代码,次次都会复线出拿锁失败的情况 @PostMapping("/testRedisLock") public Result testRedisLock(@RequestParam Integer param) { final boolean[] flag = {false}; for(int i = 0 ;i<param;i++){ //方式1:相当于继承了Thread类,作为子类重写run()实现 int finalI = i; new Thread() { @Override public void run() { try{ payV2Service.testRedisLock(finalI); }catch (Exception e){ e.printStackTrace(); flag[0] = true; throw new RuntimeException("失败:"); } //System.out.println("匿名内部类创建线程方式1..."); }; }.start(); } for(int i = 0 ;i<param;i++){ //方式1:相当于继承了Thread类,作为子类重写run()实现 int finalI = i; new Thread() { @Override public void run() { try{ payV2Service.testRedisLock(finalI); }catch (Exception e){ e.printStackTrace(); flag[0] = true; throw new RuntimeException("失败:"); } //System.out.println("匿名内部类创建线程方式1..."); }; }.start(); } return Result.succeed(flag[0],"成功"); } @Override @RedisLock(prefix = "CREATORDER:TEST") public Object testRedisLock(Integer param) { log.info("加锁成功:" + param); return true; } 假设后,可近似求证的结果 基于以上两种假设,更可能出现的结论是写重复key导致失败,假设带着改结论去论证业务代码中由于数据问题,导致key重复的可能。于是针对加锁的key,去验证数据是否有相同。