在电商平台中订单表中的数据会越来越多,为了更好的业务扩招,需要对数据库表进行拆分。
横向拆分就是根据不同的订单类型拆分为服装订单表、家电订单表和其他订单表。
纵向拆分按年份拆分,例如2018年一个表,2020年一个表。
在数据库表拆分之后,当需要数据间从多个表中查找,这就需要dubbo的提供的特性服务分组 和 分组聚合。
一个接口实现了多个不同数据库表间的查询,在dubbo中可以用group区分。
服务端中添加group属性,自定义group名称。
客户端需要调用哪个服务端提供的group组,就在reference中通过group属性,声明哪一个。
如果客户端只需要调用feedback
组,这里只需要声明feedback
组即可,
也可以通过group=“*”
, 调用任意一个可以组的实现。
上面只是对服务进行分组,而dubbo还提供了,服务调用的结果聚合。比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。
如果需要所有分组进行聚合,只需要将group
设置为*
, 添加merger
属性为true
。
合并指定分组,如果只需要将aaa,bbb进行结果合并,示例如下:
指定方法合并结果,其他未指定的方法,将只调用一个group。
某个方法不合并结果,其它都合并结果。
指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称。
如果两个服务的返回值类型不一样,需要merger="mymerge"
通过指定一个合并器的类型。
dubbo提供的合并器有:
org.apache.dubbo.rpc.cluster.merger.ArrayMerger
org.apache.dubbo.rpc.cluster.merger.ListMerger
org.apache.dubbo.rpc.cluster.merger.SetMerger
org.apache.dubbo.rpc.cluster.merger.MapMerger
在正式环境中,新版本上线时,会和老版本共同发布在正式业务中,在正式环境中小范围的测试一段时间,测试新版本的稳定性。
可以通过分组,进行服务的隔离调用,也可以通过dubbo中的多版本,来控制服务间的隔离。
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务间不引用。
可以按照以下的步骤进行版本迁移:
老版本服务提供者配置:
新版本服务提供者配置:
老版本服务消费者配置:
新版本服务消费者配置:
如果不需要区分版本,可以按照以下的方式配置 [1]:
在生产环境中,比如服务器只能处理1万个连接请求,如果超过1万个连接请求,就会给服务器造成压力,导致系统不可用。为了保证服务的高可用,我们需要对请求进行限流。
将所有请求,保存在一个队列桶中,然后以固定的频率,从桶中取出请求。
下面是从网上找的两张图来描述令牌桶算法,令牌以固定频率向桶中添加令牌,如果桶满了,令牌丢弃。
当有请求进来时,从桶中拿到令牌,才能进行访问。如果拿不到令牌拒绝访问。
如果10秒内,没有任何请求,突然进来1万个请求,漏桶算法还是以固定的频率进行请求,而令牌算法桶中如果有1万个令牌,这1万个请求就会都请求成功。后续再有别的请求时,就会被拒绝。令牌桶算法对业务峰值有很好的承载能力。
示例: 简单实现一个令牌桶算法。
public class TokenBucket {
//桶的容量
private int bucketNums = 100;
//流入速度
private int rate = 1;
// 当前令牌数量
private int nowTokens;
// 时间
private long timestamp;
private long getNowTime() {
return System.currentTimeMillis();
}
private int min(int tokens) {
if (bucketNums > tokens) {
return tokens;
} else {
return bucketNums;
}
}
public boolean getToke() {
// 记录来拿令牌的时间
long nowTime = getNowTime();
// 添加令牌 【判断该有多少个令牌】
nowTokens = nowTokens + (int)((nowTime - timestamp) * rate);
// 添加以后的令牌数量与桶的容量那个小
nowTokens = min(nowTokens);
// 修改拿令牌的时间
timestamp = nowTime;
// 判断令牌是否足够。
if (nowTokens >= 1) {
nowTokens -= 1;
return true;
} else {
return false;
}
}
public static void main(String[] args) {
TokenBucket tokenBucker = new TokenBucket();
}
}
业务中的使用:请求进来时,去获取令牌,获取成功,返回true。
private static TokenBucket bucket = new TokenBucket();
/**
* 下订单购票
* @return
*/
@RequestMapping(value = "buyTickets", method = RequestMethod.POST)
public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
try {
if (bucket.getToke()) {
// 验证售出的票是否为真。
boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
// 已经销售的座位里,有没有这些座位。
boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
// 验证上述两个内容有一个不为真,则不创建订单信息。
if (trueSeats && notSoldSeats) {
// 创建订单信息
String userId = CurrentUser.getUserId();
if (StringUtils.isBlank(userId)) {
return ResponseVO.serviceFail("用户未登录!");
}
OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
if (orderVO == null) {
log.error("购票业务异常");
return ResponseVO.serviceFail("购票业务异常");
} else {
return ResponseVO.success(orderVO);
}
} else {
log.error("购票业务异常");
return ResponseVO.serviceFail("订单中座位编号有问题");
}
} else {
return ResponseVO.serviceFail("购票人数过多,请稍后再试!");
}
} catch (Exception e) {
log.error("购票业务异常", e);
return ResponseVO.serviceFail("购票业务异常");
}
}
在多服务环境中,比如订单服务会调用商品服务、用户服务、积分服务多个服务,如果商品服务挂掉了或者服务调用超时等问题,下订单服务就会因为长时间请求导致下单失败,导致用户体验不佳。此时我们就可以通过熔断降级的方式,不是直接给用户反回一个异常,而是返回一个网络不佳呀,或者其他问题。给用户一个折中、最小影响的方案返回。
1、 添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
2.0.0.RELEASE
2、 启动类添加注解
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
@SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"})
@EnableDubboConfiguration
@EnableAsync
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3、 在需要熔断的方法上,添加注解配置和添加降级回调方法。
@HystrixCommand(fallbackMethod = "error", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
},
threadPoolProperties = { // hystrix 中线程池配置
@HystrixProperty(name = "coreSize", value = "1"),
@HystrixProperty(name = "maxQueueSize", value = "10"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "1000"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "8"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1500")
})
@RequestMapping(value = "buyTickets", method = RequestMethod.POST)
public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
try {
if (bucket.getToke()) {
// 验证售出的票是否为真。
boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
// 已经销售的座位里,有没有这些座位。
boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
// 验证上述两个内容有一个不为真,则不创建订单信息。
if (trueSeats && notSoldSeats) {
// 创建订单信息
String userId = CurrentUser.getUserId();
if (StringUtils.isBlank(userId)) {
return ResponseVO.serviceFail("用户未登录!");
}
OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
if (orderVO == null) {
log.error("购票业务异常");
return ResponseVO.serviceFail("购票业务异常");
} else {
return ResponseVO.success(orderVO);
}
} else {
log.error("购票业务异常");
return ResponseVO.serviceFail("订单中座位编号有问题");
}
} else {
return ResponseVO.serviceFail("购票人数过多,请稍后再试!");
}
} catch (Exception e) {
log.error("购票业务异常", e);
return ResponseVO.serviceFail("购票业务异常");
}
}
自定义熔断方法,和fallbackMethod属性的值和定义的方法名一致。
/**
* 注解在哪个方法上,返回值和方法参数一定要一致。
* @param fieldId
* @param soldSeats
* @param seatsName
* @return
*/
public ResponseVO error(Integer fieldId, String soldSeats, String seatsName) {
return ResponseVO.serviceFail("抱歉,下单人太多,请稍后再试!");
}