1.微服务拆分的起点
1.1如何拆分微服务?
现在微服务概念炒得很热,关于如何拆分微服务,有以下几点。
- 1.先明白起点和终点
起点:既有架构的形态。
终点:好的架构不是设计出来的,而是进化而来的。 - 典型架构
1.2 适合上微服务么?
业务形态不适合的
1.系统中包含很多很多强事务场景的(因为微服务是分布式的,分布式强事务CAP最多也就能达到最终一致性)
2.业务相对稳定、迭代周期长
3.访问压力不大、可用性要求不高(中小型企业的OA)
2.康威定律和微服务
2.1 康威定律
除了业务形态不适合的,其实还有其他条件,很可能导致不适合迁移到微服务中去,首先我们看一下微服务理论基础:康威定律。
一句话概括就是:沟通的问题会影响系统设计
所以微服务都是强调小团队开发,大的系统拆分成微服务时候,大的团队随机也会拆分成小的团队。
2.2 微服务和团队结构
- 微服务特点
- 传统vs微服务
3.点餐业务服务拆分分析
点餐系统分为:买家端 和 卖家端
3.1 服务拆分
上面:服务两种方式拆分:
第一种(按照终端):买家端(手机端)的ui单独为一个服务放到nginx里面,卖家端(PC端)的ui单独为一个服务,两个服务同时向后端的通用服务请求数据
第二种(按照业务):将订单ui,商品ui,支付ui都放到一个边缘服务。
以上两种都不对,如果只是自己运营的的点餐应用:团队只有一个,并且业务变化也不大,没有微服务化的必要。如果是一个快速发展的IT公司点餐部门,业务快速发展,需求不断提出,所以需要拆分成微服务。
所以说:起点和团队结构,沟通方式都会决定微服务的设计。
3.2 服务拆分方法论
下图出自<<可扩展的艺术>>书籍
- X轴 水平复制:通过应用程序扩展,通过负载均衡运行多个父本一样的应用程序
- Z轴 数据分区:将服务按数据分区、每个服务器负责一个数据子集、每个服务器运行的代码是一样的
- Y轴 功能解耦:不同职责模块分成不同服务
通过以上模型,知道服务拆分的两个关键职责:功能和数据
3.2.1 如何拆”功能”
- 1.单一职责(每个服务负责业务功能的单独一部分)、松耦合(服务之间耦合度低,修改一个服务不用导致另一个服务修改)、高内聚(服务内部相关行为都聚集在一个服务内,而不是分散在不同服务中心)。
- 2.关注点分离:-按职责 -按通用性 -按粒度级别(微服务并不是越小越好,要合适)
3.2.2 如何拆”数据”
拆分功能和拆分数据是有先后顺序
- 1.先考虑业务功能、再考虑业务功能对应的数据
- 2.无状态服务(状态:如果一个数据要被多个服务共享才能完成一个请求,那么这个数据就称为状态,进而依赖这个状态数据的服务称为有状态服务)
如上所示把业务数据存放到有状态服务的数据库,缓存中实现前端微服务和后端微服务质检无状态服务,数据间没有太大耦合性。
如何拆数据:
1.每个微服务都有单独数据存储(微服务共有数据库,有可能其中一个数据库出现问题会影响其他微服务,一个服务要获取另一个服务的数据,不能直接连接库去请求,而是调用另一个服务接口去获取数据,服务之间有隔离)
2.依据服务特点选择不同结构的数据库类型(如果:数据基于搜索的,那么es合适,如果是非机构化数据,那么nosql的mongodb合适)
3.难点在确定边界
3.2.3 点餐业务服务拆分分析
1.不要期望服务拆分一次就正确,微服务是不断演进的
4.商品服务
4.1 商品服务api和sql介绍
商品列表
|
|
参数
|
|
返回
|
|
– 类目
CREATE TABLE product_category
(
category_id
INT NOT NULL AUTO_INCREMENT,
category_name
VARCHAR(64) NOT NULL COMMENT ‘类目名字’,
category_type
INT NOT NULL COMMENT ‘类目编号’,
create_time
TIMESTAMP NOT NULL COMMENT ‘创建时间’,
update_time
TIMESTAMP NOT NULL COMMENT ‘修改时间’,
PRIMARY KEY (category_id
),
UNIQUE KEY uqe_category_type
(category_type
)
);
– 商品
CREATE TABLE product_info
(
product_id
VARCHAR(32) NOT NULL,
product_name
VARCHAR(64) NOT NULL COMMENT ‘商品名称’,
product_price
DECIMAL(8,2) NOT NULL COMMENT ‘单价’,
product_stock
INT NOT NULL COMMENT ‘库存’,
product_description
VARCHAR(64) COMMENT ‘描述’,
product_icon
VARCHAR(512) COMMENT ‘小图’,
product_status
TINYINT(3) DEFAULT ‘0’ COMMENT ‘商品状态,0正常1下架’,
category_type
INT NOT NULL COMMENT ‘类目编号’,
create_time
TIMESTAMP NOT NULL COMMENT ‘创建时间’,
update_time
TIMESTAMP NOT NULL COMMENT ‘修改时间’,
PRIMARY KEY (product_id
)
);
POST /order/create
name: “张三”
phone: “18868822111”
address: “总部”
openid: “ew3euwhd7sjw9diwkq” //用户的微信openid
items: [{
productId: “1423113435324”,
productQuantity: 2 //购买数量
}]
|
|
{
“code”: 0,
“msg”: “成功”,
“data”: {
“orderId”: “147283992738221”
}
}
public class KeyUtil {
/**
* 生成唯一的主键
* 格式: 时间+随机数
*/
public static synchronized String genUniqueKey(){
Random random = new Random();
Integer number = random.nextInt(900000)+100000;
return System.currentTimeMillis()+String.valueOf(number);
}
}
public class OrderForm {
/**
* 买家姓名
*/
@NotEmpty(message = "姓名必填")
private String name;
/**
* 买家手机号
*/
@NotEmpty(message = "手机号必填")
private String phone;
/**
* 买家地址
*/
@NotEmpty(message = "地址必填")
private String address;
/**
* 买家微信openid
*/
@NotEmpty(message = "openid必填")
private String openid;
/**
* 购物车
*/
@NotEmpty(message = "购物车不能为空")
private String items;
}
|
|
public class OrderException extends RuntimeException {
private Integer code;
public OrderException(Integer code, String message){
super(message);
this.code = code;
}
public OrderException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
@Getter
public enum ResultEnum {
PARAM_ERROR(1, “参数错误”),
CART_EMPTY(2, “购物车为空”)
;
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
- 接口返回的结果data里面只有一个字段,所以不需要创建一个对象,一个封装一个map返回就行
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 1.参数校验
* 2.查询商品信息(调用商品服务)
* 3.计算总价
* 4.扣库存(调用商品服务)
* 5.订单入库
*/
@PostMapping("/create")
private Object create(@Valid OrderForm orderForm,
BindingResult bindingResult){
//检验不通过抛出自定义异常
if (bindingResult.hasErrors()){
//https://www.cnblogs.com/weiapro/p/7633645.html
log.error("【创建订单】参数不正确, orderForm={}", orderForm);
throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
//orderForm->orderDTO
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if(CollectionUtils.isEmpty(orderDTO.getOrderDetailList())){
log.error("【创建订单】购物车信息为空");
throw new OrderException(ResultEnum.CART_EMPTY);
}
OrderDTO result = orderService.create(orderDTO);
Map<String, String> map = new HashMap<>();
map.put("orderId", result.getOrderId());
return ResultVOUtil.success(map);
}
}