订单服务
功能如下:
用户服务
功能如下:
创建maven项目
设置项目为父类型
pom
创建子项目 Dubbo-producer(生产者)
引入依赖
<dependency>
<groupId>io.dubbo.springbootgroupId>
<artifactId>spring-boot-starter-dubboartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.7version>
dependency>
父项目加入配置
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.2version>
<relativePath/>
parent>
父项目加入模块
在pom.xml文件中加入
<modules>
<module>dubbo-providermodule>
modules>
创建子项目Dubbo-consumer(消费者)
父项目加入模块
<modules>
<module>dubbo-providermodule>
<module>dubbo-consumermodule>
modules>
添加依赖
<dependency>
<groupId>io.dubbo.springbootgroupId>
<artifactId>spring-boot-starter-dubboartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.7version>
dependency>
application.properties配置文件
# 应用服务 WEB 访问端口
# 端口号
server.port=80
# 1. 配置项目名称
spring.dubbo.application.name=user-service
# 2. 配置注册中心地址
spring.dubbo.registry.address=zookeeper://192.168.66.100
spring.dubbo.registry.port=2181
# 3. 指定dubbo使用的协议、端口
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
# 4. 指定注册到zk上超时时间,ms
spring.dubbo.registry.timeout=10000
# 5. 配置Dubbo包扫描
spring.dubbo.scan=com.jjy.service
代码实现:
pojo层
Order类
package com.jjy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 订单模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {
// 订单id
private Long id;
// 用户id
private Long userId;
// 订单总价格
private Double prict;
// 收货人手机号
private String mobile;
// 收货人地址
private String address;
// 支付类型 1:微信 2:支付宝
private Integer pay_method;
}
CommonResult类
package com.jjy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 统一返回结果集
* @param
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> implements Serializable {
//1.返回结果编码
private Integer code;
//2.返回结果描述
private String message;
//3.数据
// 数据
private T data;
}
Service层
IOrderService接口
package com.jjy.service;
import com.jjy.pojo.CommonResult;
import com.jjy.pojo.Order;
public interface IOrderService {
//1.创建订单
CommonResult createOrders(Order orders);
//2.根据用户ID查询订单详情
CommonResult findByuserId(Long userid);
}
OrderServiceImpl类
package com.jjy.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.jjy.pojo.CommonResult;
import com.jjy.pojo.Order;
import com.jjy.service.IOrderService;
@Service// 将这个类提供的方法(服务) 对外发布。将访问的地址 ip 端口 路径 注册到注册中心
public class OrderServiceImpl implements IOrderService {
@Override
public CommonResult createOrders(Order orders) {
CommonResult commonResult = new CommonResult();
// 返回结果编码
commonResult.setCode(200);
// 返回结果描述信息
commonResult.setMessage("创建成功");
return commonResult;
}
@Override
public CommonResult findByuserId(Long userid) {
//TODO 模拟数据库操作
CommonResult commonResult = new CommonResult();
// 返回结果编码
commonResult.setCode(200);
// 返回结果描述信息
commonResult.setMessage("查询成功");
// 返回结果集
Order orders = new Order();
orders.setId(1L);
orders.setUserId(1L);
orders.setPrict(121.1);
orders.setMobile("18588888888");
orders.setAddress("北京市海淀区中关村");
orders.setPay_method(1);
commonResult.setData(orders);
return commonResult;
}
}
application.properties配置文件
# 端口号
server.port=8080
# 1. 配置项目名称
spring.dubbo.application.name=user-consumer
# 2. 配置注册中心地址
spring.dubbo.registry.address=zookeeper://192.168.66.100
spring.dubbo.registry.port=2181
# 3. 指定dubbo使用的协议、端口
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20881
# 4. 指定注册到zk上超时时间,ms
spring.dubbo.registry.timeout=10000
# 5. 配置Dubbo包扫描
spring.dubbo.scan=com.jjy.service
pojo层
User类
package com.jjy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
//用户id
private Long id;
// 用户名字
private String name;
}
Service层
IUserService接口
package com.jjy.service;
import com.jjy.pojo.CommonResult;
import com.jjy.pojo.Order;
public interface IUserService {
//根据用户id查询订单详情
CommonResult findByUserId(Long userId);
}
UserService类
package com.jjy.service.impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jjy.pojo.CommonResult;
import com.jjy.pojo.Order;
import com.jjy.service.IOrderService;
import com.jjy.service.IUserService;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class UserService implements IUserService {
//引入订单服务 远程调用
/**
*
* @Autowired : 本地注入
*
* 1. 从zookeeper注册中心获取IOrderService访问的url。
* 2. 进行远程调用RPC。
* 3. 将结果封装为一个代理对象,交给这个变量赋值。
*
*/
@Reference
private IOrderService iOrderService;
@Override
public CommonResult findByUserId(Long userId) {
return iOrderService.findByuserId(userId);
}
}
Controller层
UserController类
package com.jjy.controller;
import com.jjy.pojo.CommonResult;
import com.jjy.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private IUserService iUserService;
@GetMapping("findByUserId")
public CommonResult findByUserId(Long userId){
return iUserService.findByUserId(userId);
}
}
访问 http://localhost:8080/findByUserId?userId=1
修改配置文件
在.idea/workspace.xml 文件中找到
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
RuleState>
list>
option>
component>
添加配置
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
RuleState>
list>
option>
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
set>
option>
component>
网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。
总结:
序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。
流程:
不妨借用个例子帮助你理解,比如发快递,我们要发一个需要自行组装的物件。发件人发之前,会把物件拆开装箱,这就好比序列化;这时候快递员来了,不能磕碰呀,那就要打包,这就好比将序列化后的数据进行编码,封装成一个固定格式的协议;过了两天,收件人收到包裹了,就会拆箱将物件拼接好,这就好比是协议解码和反序列化。
注册中心挂了,服务是否可以正常访问?
答案:
因为dubbo服务消费者在第一次调用时
,会将服务提供方地址缓存到本地
,以后
在调用则不会访问注册中心。服务提供者地址发生变化时,注册中心会通服务消费者。
演示
关闭注册中心
./zkServer.sh stop
远程调用服务
http://localhost:8080/findByUserId?userId=1
问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
- dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接
配置超时时间
服务生产者端
使用timeout属性配置超时时间,默认值1000,单位毫秒。
@Service(timeout = 3000) //当前服务3秒超时
public class OrderServiceImpl implements IOrderService {
修改服务提供者制造延迟
@Override
public CommonResult<Order> findByUserId(Long userId) throws InterruptedException {
CommonResult commonResult = new CommonResult();
// 返回结果编码
commonResult.setCode(200);
// 返回结果描述信息
commonResult.setMessage("查询成功");
// 返回结果集
//TODO 模拟数据库操作 突然数据库操作很慢
Thread.sleep(4000);
Order order = new Order();
order.setId(1L);
order.setUserId(1L);
order.setPrict(121.1);
order.setMobile("18588888888");
order.setAddress("北京市海淀区中关村");
order.setPay_method(1);
commonResult.setData(order);
return commonResult;
}
消费端(优先级大)
@Reference(timeout = 2000)// 远程注入
private IOrderService iOrderService;
报错
com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout
com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:107) ~[dubbo-2.5.3.jar:2.5.3]
com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:84) ~[dubbo-2.5.3.jar:2.5.3]
com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:96) ~[dubbo-2.5.3.jar:2.5.3]
超时设置的优先级
上面有提到dubbo支持多种场景下设置超时时间,也说过超时是针对消费端的。那么既然超时是针对消费端,为什么服务端也可以设置超时呢?
总结:
这其实是一种策略,其实服务端的超时配置是消费端的缺省配置,即如果服务端设置了超时,任务消费端可以不设置超时时间,简化了配置。另外针对控制的粒度,Dubbo支持了接口级别也支持方法级别,可以根据不同的实际情况精确控制每个方法的超时时间。
超时问题:
如果出现网络抖动,则会出现请求失败。
如何解决
Dubbo提供重试机制来避免类似问题的发生。
重试机制配置
@Service(timeout = 3000,retries = 2)
注意:
Dubbo在调用服务不成功时,默认会重试2次。
Dubbo提供多版本的配置,方便我们做服务的灰度发布,或者是解决不兼容的问题。
灰度发布(金丝雀发布):
当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
版本迁移步骤
多版本配置
老版本服务提供者配置
@Service(version = "1.0.0")
public class OrderServiceImpl implements IOrderService {
public CommonResult findByUserId(Long id) {
CommonResult<Orders> commonResult = new CommonResult();
// 返回结果编码
commonResult.setCode(200);
// 返回结果描述
commonResult.setMessage("success");
// TODO 模拟数据库操作
// 数据
Orders orders = new Orders();
orders.setId(1L);
orders.setUserId(2L);
orders.setPrict(12.5);
orders.setMobile("1888888888");
orders.setAddress("北京");
orders.setPay_method(1);
commonResult.setData(orders);
return commonResult;
}
}
新版本服务提供者配置
@Service(version = "2.0.0")
public class OrderServiceImpl implements IOrderService {
public CommonResult findByUserId(Long id) {
CommonResult<Orders> commonResult = new CommonResult();
// 返回结果编码
commonResult.setCode(200);
// 返回结果描述
commonResult.setMessage("success");
// TODO 模拟数据库操作
// 数据
Orders orders = new Orders();
orders.setId(1L);
orders.setUserId(2L);
orders.setPrict(12.5);
orders.setMobile("1888888888");
orders.setAddress("北京");
orders.setPay_method(1);
commonResult.setData(orders);
return commonResult;
}
}
新版本服务消费者配置
@Reference(version = "2.0.0")
private IOrderService iOrderService;// 订单服务
如果不需要区分版本,可以按照以下的方式配置 :
@Reference(version = "*")
private IOrderService iOrderService;// 订单服务
Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务通常会部署多个实例。
问题:
订单服务生产者会出现单点故障。
如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。
Dubbo内置负载均衡策略
RandomLoadBalance
:随机负载均衡,随机的选择一个,默认负载均衡。RoundRobinLoadBalance
:轮询负载均衡。LeastActiveLoadBalance
:最少活跃调用数,相同活跃数的随机。ConsistentHashLoadBalance
:一致性哈希负载均衡,相同参数的请求总是落在同一台机器上。负载均衡策略配置
如果不指定负载均衡,默认使用随机负载均衡。我们也可以根据自己的需要,显式指定一个负载均衡。
生产者服务
@Service(timeout = 3000,retries = 3,loadbalance = "roundrobin")
消费者服务
@Reference(timeout = 2000,loadbalance = "roundrobin"
参数:
- random:随机负载均衡
- leastactive:最少活跃调用数,相同活跃数的随机
- roundrobin:轮询负载均衡
- consistenthash:一致性哈希负载均衡
查看负载均衡配置
Dubbo框架为服务集群容错提供了一系列好的解决方案,在此称为dubbo服务集群容错模式。
容错模式
Failover Cluster
:失败重试。默认值。当出现失败,重试其它服务器,默认重试2次,使用retries配置。一般用于读操作Failfast Cluster
: 快速失败,只发起一次调用,失败立即报错。通常用于写操作。Failsafe Cluster
: 失败安全,出现异常时,直接忽略。返回一个空结果。日志不重要操作。Failback Cluster
: 失败自动恢复,后台记录失败请求,定时重发。非常重要的操作。Forking Cluster
:并行调用多个服务器,只要有一个成功即返回。Broadcast Cluster
:广播调用所有提供者,逐个调用,任意一台报错则报错。 同步要求高的可以使用这个模式。集群容错配置
在消费者服务配置
@Reference(cluster = "failover")
private IOrderService iOrderService;
什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。(关闭一些不重要的服务,保证核心服务正常运行)
两种场景:
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
为什么需要降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
服务降级方式
第一种
mock=force:return null
含义:
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
第二种
mock=fail:return null
含义:
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
服务降级演示
@Reference(timeout = 2000,mock = "force:return null")
private IOrderService iOrderService;
生活中的限流
春运,一项全人类历史上最大规模的迁移活动,抢火车票一直是每年跨年以后的热点话题。
限流算法
漏桶算法
原理:
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
原理:
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
漏桶 vs 令牌桶的区别
漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,但是导向后台系统的压力也会相应增多。
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
并发控制
@Service(executes = 10)(生产者producer)
注意:
服务端并发执行(或占用线程池线程数)不能超过10个
连接控制
@Service(actives= 10)(生产者producer)
注意:
占用连接的请求的数不能超过10个。
结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓存,以减少用户加缓存的工作量。
Dubbo提供了三种结果缓存机制:
配置缓存
@Reference(cache="lru")
测试结果缓存
在服务提供者工程中添加原子类操作
private AtomicInteger index = newAtomicInteger();
原子的方式将当前值加1
Orders orders = new Orders();
orders.setId(1L);
orders.setUserId(2L);
orders.setPrict(12.5);
orders.setMobile("1888888888");
Integer callCount = index.getAndIncrement();
orders.setAddress("北京" +callCount);
orders.setPay_method(1);
commonResult.setData(orders);
return commonResult;
在服务消费者使用cache属性
@Reference(cache= "lru")