3.4 生产者代码编写
1.创建订单实体类
package com.zj.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; }
网络中对象的传递是字节流因此需要实现序列化接口。
2.编写订单接口
package com.zj.service; import com.zj.pojo.CommonResult; import com.zj.pojo.Order; public interface IOrderService { //创建订单 CommonResult<Order> createOrders(Order order); //根据用户id查询订单详情 CommonResult<Order> findByUserId(Long userId); }
3.创建统一返回结果集实体类
/** * 统一返回结果集 * @param <T> */ @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> implements Serializable { // 返回结果编码 private Integer code; // 返回结果描述 private String message; // 数据 private T data; private CommonResult(Integer code,String message){ this(code,message,null); } }
4.编写订单业务实现类
package com.zj.service; import com.alibaba.dubbo.config.annotation.Service; import com.zj.pojo.CommonResult; import com.zj.pojo.Order; @Service public class OrderServiceImpl implements IOrderService { /** * 创建订单 * @param order */ @Override public CommonResult<Order> createOrders(Order order) { //TODO 模拟数据库操作 CommonResult<Order> result = new CommonResult<Order>(); result.setCode(200); result.setMessage("订单创建成功!"); result.setData(null); return result; } /** * 根据用户id查询订单 * @param userId * @return */ @Override public CommonResult<Order> findByUserId(Long userId) { //TODO 模拟数据库操作 CommonResult<Order> commonResult = new CommonResult<Order>(); // 返回结果编码 commonResult.setCode(200); // 返回结果描述信息 commonResult.setMessage("查询成功"); // 返回结果集 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; } }
注意:
@Service注解的作用:将这个类提供的方法对外发布,将访问该方法的地址IP,端口路径注册到注册中心zookeeper
该注解不是spring的注解而是dubbo的注解:
com.alibaba.dubbo.config.annotation.Service;
5.生产者编写配置文件
#配置项目名称 spring.dubbo.application.name = order-producer #配置注册中心(告诉项目注册中心在哪) spring.dubbo.registry.address=zookeeper://192.168.66.100 spring.dubbo.registry.port=2181 #指定Dubbo使用的协议和端口号 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20880 #指定注册到zookeeper上的超时时间 spring.dubbo.registry.timeout=10000 #配置Dubbo包扫描 spring.dubbo.scan=com.zj.service
6.启动生产者项目
3.5消费者工程配置
1.创建服务消费者SpringBoot项目模块
2.为消费者项目指定父项目
<parent> <groupId>com.zj</groupId> <artifactId>dubbo-demo</artifactId> <version>1.0-SNAPSHOT</version> </parent>
3. 指定父项目的子模块
<modules> <module>dubbo-producer</module> <module>dubbo-consumer</module> </modules>
4.在消费者模块引入依赖
<!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 整合dubbo --> <dependency> <groupId>io.dubbo.springboot</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.0.0</version> </dependency> <!-- zookeeper客户端 --> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.7</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>>
因为消费者需要调用生产者的接口.
3.6 消费者代码编码
1.编写用户实体类
/** * 用户模型 */ @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { //用户id private Long id; // 用户名字 private String name; }
2.编写用户接口
/** * 用户接口 */ public interface IUserService { //根据用户id查询订单详情 CommonResult<Order> findByUserId(Long id); }
3.编写用户接口实现类
package com.zj.service; import com.alibaba.dubbo.config.annotation.Reference; import com.zj.pojo.CommonResult; import com.zj.pojo.Order; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements IUserService { //引入订单服务(远程调用) @Reference //从zookeeper注册中心获取访问IOrderService的url,通过rpc协议远程调用,将结果封装为代理对象赋给变量 private IOrderService iOrderService; /** * 根据用户id查询订单 * @param id 用户id * @return */ @Override public CommonResult<Order> findByUserId(Long id) { return iOrderService.findByUserId(id); } }
这里的service注解是spring注解因为需要controller调用service层.
4.编写用户控制层
package com.zj.controller; import com.zj.pojo.CommonResult; import com.zj.pojo.Order; import com.zj.service.IUserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * 用户控制层 */ @RestController public class UserController { @Resource private IUserService iUserService; /** * 根据用户ID查询用户订单详情 * @param userId 用户id * @return */ @GetMapping("findByUserId") public CommonResult<Order> findByUserId(Long userId){ return iUserService.findByUserId(userId); } }
5.加入Dubbo配置
#配置项目名称 spring.dubbo.application.name = order-consumer #配置注册中心(告诉项目注册中心在哪) spring.dubbo.registry.address=zookeeper://192.168.66.100 spring.dubbo.registry.port=2181 #指定Dubbo使用的协议和端口号 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20881 #指定注册到zookeeper上的超时时间 spring.dubbo.registry.timeout=10000 #配置Dubbo包扫描 spring.dubbo.scan=com.zj.service #端口 server.port=8081
6.访问localhost:8081/find/1
3.7 idea开启Dashboard面板
普通的Run面板
Run Dashboard面板
修改配置文件
在.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> <option name="configurationTypes"> <set> <option value="SpringBootApplicationConfigurationType" /> </set> </option> </component>
四.Dubbo高级特性
4.1 序列化协议安全
为什么需要序列化
网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。
总结:
序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。
序列化反序列过程
流程:
不妨借用个例子帮助你理解,比如发快递,我们要发一个需要自行组装的物件。发件人发之前,会把物件拆开装箱,这就好比序列化;这时候快递员来了,不能磕碰呀,那就要打包,这就好比将序列化后的数据进行编码,封装成一个固定格式的协议;过了两天,收件人收到包裹了,就会拆箱将物件拼接好,这就好比是协议解码和反序列化。
4.2 地址缓存
地址缓存
(面试)注册中心挂了,服务是否可以正常访问?
答案:
可以.因为dubbo服务消费者在
第一次调用时
,会将服务提供方地址缓存到本地
,以后在调用则不会访问注册中心。服务提供者地址发生变化时,注册中心会通服务消费者。
4.3 超时时间和覆盖关系
超时机制
问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
- dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
配置超时时间
生产者端设置超时时间(建议)
使用timeout属性配置超时时间,默认值1000,单位毫秒。
@Service(timeout = 3000) //当前服务3秒超时 public class OrderServiceImpl implements IOrderService {}
消费端设置超时时间(不建议)
@Reference(timeout = 2000)// 远程注入 private IOrderService iOrderService;
4.4重试机制
超时问题:
如果出现网络抖动,则会出现请求失败。
如何解决
Dubbo提供重试机制来避免类似问题的发生。
重试机制配置
@Service(timeout = 3000,retries = 2)
注意:
Dubbo在调用服务不成功时,默认会重试2次。
4.5 多版本灰度发布
Dubbo提供多版本的配置,方便我们做服务的灰度发布,或者是解决不兼容的问题。
灰度发布(金丝雀发布):
当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。就是王者的体验服!!
版本迁移步骤
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置
@Service(version = "1.0.0")
新版本服务提供者配置
@Service(version = "2.0.0")
新版本服务消费者配置
@Reference(version = "2.0.0") private IOrderService iOrderService;// 订单服务
如果不需要区分版本,可以按照以下的方式配置 :
@Reference(version = "*") private IOrderService iOrderService;// 订单服务
4.6 负载均衡
Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务通常会部署多个实例。
问题:
如果服务B的请求量很大导致服务器宕机,则订单服务生产者会出现单点故障。如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。
Dubbo内置负载均衡策略
如果增加服务,搭建服务的集群的话,消费者E到底调用的是哪一个服务器的服务呢?Dubbo为解决该问题提供了一个方案,也就是负载均衡.
- RandomLoadBalance:随机负载均衡,随机的选择一个,默认负载均衡。
- RoundRobinLoadBalance:轮询负载均衡。
- LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。
- ConsistentHashLoadBalance:一致性哈希负载均衡,相同参数的请求总是落在同一台机器上。
负载均衡策略配置
生产者服务
@Service(timeout = 3000,retries = 3,loadbalance = "roundrobin")
消费者服务
@Reference(timeout = 2000,loadbalance = "roundrobin") private IOrderService iOrderService;
参数:
- random:随机负载均衡
- leastactive:最少活跃调用数,相同活跃数的随机
- roundrobin:轮询负载均衡
- consistenthash:一致性哈希负载均衡
4.7 集群容错
集群容错模式
Dubbo框架为服务集群容错提供了一系列好的解决方案,在此称为dubbo服务集群容错模式。
容错模式
- Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器,默认重试2次,使用retries配置。一般用于读操作
- Failfast Cluster : 快速失败,只发起一次调用,失败立即报错。通常用于写操作。
- Failsafe Cluster : 失败安全,出现异常时,直接忽略。返回一个空结果。日志不重要操作。
- Failback Cluster : 失败自动恢复,后台记录失败请求,定时重发。非常重要的操作,发送请求到成功未知。
- Forking Cluster:并行调用多个服务器,只要有一个成功即返回。
- Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。 同步要求高的可以使用这个模式。
集群容错配置
在消费者服务配置
@Reference(cluster = "failover") private IOrderService iOrderService;
4.8 服务降级
什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,关闭除了核心业务之外的服务以此释放服务器资源以保证核心任务的正常运行。
两种场景:
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
为什么需要降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
服务降级方式
第一种:mock = "force:return null"
@Reference(timeout = 2000,mock = "force:return null") private IOrderService iOrderService;
含义:
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
第二种 :mock = "mock=fail:return null"
@Reference(timeout = 2000,mock = "mock=fail:return null") private IOrderService iOrderService;
含义:
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
4.9 服务限流
生活中的限流
春运,一项全人类历史上最大规模的迁移活动,抢火车票一直是每年跨年以后的热点话题。例如,通过一系列复杂的验证手段来实现用户的限流。
限流算法
漏桶算法
原理:
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法
原理:
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
漏桶 vs 令牌桶的区别
漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,但是导向后台系统的压力也会相应增多。
服务限流实现
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
并发控制
服务生产者添加如下注解
@Service(executes = 10)
注意:
服务端并发执行(或占用线程池线程数)不能超过10个
连接控制
@Service(actives= 10)
注意:
占用连接的请求的数不能超过10个。
4.10 结果缓存
结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓 存,以减少用户加缓存的工作量。
Dubbo提供了三种结果缓存机制:
- lru: 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
- threadlocal: 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信 息,通过线程缓存,可以减少这种多余访问。
- jcache: 与JSR107集成,可以桥接各种缓存实现。
配置缓存
@Reference(cache="lru")