网络7层协议如图所示.
层级关系与对应的协议.
区别:
1.RPC是传输层协议(4层).而HTTP协议是应用层协议(7层).
2.RPC协议可以直接调用中立接口,HTTP协议不可以.
3.RPC通信协议是长链接,HTTP协议一般采用短连接需要3次握手(可以配置长链接添加请求头Keep-Alive: timeout=20).
(长连接,指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。)
4.RPC协议传递数据是加密压缩传输.HTTP协议需要传递大量的请求头信息.
5.RPC协议一般都有注册中心.有丰富的监控机制.
Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架(SOA),使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 [2] Spring框架无缝集成。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
通俗的介绍一下,Dubbo是基于java实现RPC的框架,Dubbo 工作分为 4 个角色,分别是服务提供者、服务消费者、注册中心、和监控中心。如下图是官网的架构图(摘自官网)
调用关系说明
仔细一看,我们会发现此架构跟生产者-消费者模型很相似.此框架多了注册中心和监控中心,
用我们的项目再详细讲解一下:
jt-cart:购物车功能模块,jt-order:订单功能模块
调用过程:
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
简单来说 zookeeper用来注册服务和进行负载均衡,哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是ip地址和服务名称的对应关系。
总结:Zookeeper是分布式服务调度器.它是一个为分布式应用提供一致性服务的软件
关于一致性说明:zookeeper中必须满足一致性的要求.防止因为主机意外宕机,而用户使用错误的数据. zookeeper是CP原则(请看下面介绍)
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
C:一致性: 在分布式系统中的所有数据备份,在同一时刻是否同样的值。
A:可用性: 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
P:分区容错性: 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
下载地址:http://mirrors.hust.edu.cn/apache/zookeeper/
下载的时候要选带有bin的安装包
官网地址:https://zookeeper.apache.org/releases.html
连接服务器:并上传安装包到指定位置
解压文件:
tar -xvf apache-zookeeper-3.6.1-bin.tar.gz
在zk根目录下创建文件夹data/log
mkdir data log
我嫌文件名太长,我就改了个名
到conf文件中,复制配置文件并且修改名称
cp zoo_sample.cfg zoo.cfg
修改配置(dataLogDir自行添加)
zk启动关闭命令如下
sh zkServer.sh start 或者 ./zkServer.sh start
sh zkServer.sh stop
sh zkServer.sh status
Zookeeper集群
在zookeeper根目录中创建新的文件夹zkCluster.
mkdir zkCluster
进入本文件夹中,在创建zk1 zk2 zk3文件夹
在每个文件夹里创建data/log文件夹.
mkdir {zk1,zk2,zk3}/{data,log}
分别在zk1,zk2,zk3中的data文件夹中创建新的文件myid.其中的内容依次为1/2/3,与zk节点号对应.
例如在data目录下直接输入命令 vim myid
。然后点击a,进入编辑状态,输入1,然后保存退出。就会自动创建文件
其他俩文件自行创建。
将zoo_sample.cfg 复制为zoo1.cfg之后修改配置文件.
配置完成后将zoo1.cfg复制2份.分别为zoo2.cfg zoo3.cfg
看一下zoo2.cfg的配置,端口和文件路径需要修改,其他的不变。zoo3.cfg也是这样操作
配置好后,在bin目录下执行命令
sh zkServer.sh start zoo1.cfg
sh zkServer.sh stop zoo1.cfg
sh zkServer.sh status zoo1.cfg
执行三个服务,并查看主从关系
Zookeeper集群中leader负责监控集群状态,follower主要负责客户端链接获取服务列表信息.同时参与投票.
netstat -nltp | grep 2181
kill -9 8258
创建父级项目,创建一个空的maven项目,把src目录删除
编辑pom.xml文件,打包形式为pom格式
pom</packaging>
org.springframework.boot</groupId>
spring-boot-starter-parent</artifactId>
2.1.3.RELEASE</version>
/> <!-- lookup parent from repository -->
</parent>
.version>1.8</java.version>
</properties>
<!--springBoot动态的引入springMVC全部的配置 -->
org.springframework.boot</groupId>
spring-boot-starter-web</artifactId>
</dependency>
<!--引入测试类 -->
org.springframework.boot</groupId>
spring-boot-starter-test</artifactId>
test</scope>
</dependency>
<!--添加属性注入依赖 -->
org.springframework.boot</groupId>
spring-boot-configuration-processor</artifactId>
true</optional>
</dependency>
<!--支持热部署 -->
org.springframework</groupId>
springloaded</artifactId>
1.2.8.RELEASE</version>
</dependency>
org.springframework.boot</groupId>
spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
org.projectlombok</groupId>
lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
mysql</groupId>
mysql-connector-java</artifactId>
runtime</scope>
</dependency>
<!--引入druid数据源 -->
com.alibaba</groupId>
druid</artifactId>
1.1.12</version>
</dependency>
<!--spring整合mybatis-plus -->
com.baomidou</groupId>
mybatis-plus-boot-starter</artifactId>
3.0.6</version>
</dependency>
<!--spring整合redis -->
redis.clients</groupId>
jedis</artifactId>
</dependency>
org.springframework.data</groupId>
spring-data-redis</artifactId>
</dependency>
<!--添加httpClient jar包 -->
org.apache.httpcomponents</groupId>
httpclient</artifactId>
</dependency>
<!--引入dubbo配置 -->
com.alibaba.boot</groupId>
dubbo-spring-boot-starter</artifactId>
0.2.0</version>
</dependency>
</dependencies>
创建接口项目dubbo-interface
在父级项目上创建module项目,我们选择spring boot项目
修改pom文件,将父级依赖改为它自己父级的信息,同时在父级的pom文件中添加module
同样的操作创建提供者dubbo-provider, 创建消费者dubbo-consumer
说明:由于消费者和提供者都需要接口文件的支持,所以需要依赖接口项目.
配置接口项目
创建User POJO对象
@Data
@Accessors(chain=true)
@TableName("tb_user")
//dubbo框架中对象的传递必须序列化
public class User implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 用户Id号 主键自增
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 创建时间
*/
private Date created;
/**
* 更新时间
*/
private Date updated;
}
public interface UserService {
List<User> findAll();
@Transactional
void saveUser(User user);
}
服务的提供者我们需要在dubbo-provider中完成
UserServceImpl代码:
@Service(timeout=3000) //3秒超时
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
System.out.println("我是第一个服务的提供者");
return userMapper.selectList(null);
}
@Override
public void saveUser(User user) {
userMapper.insert(user);
}
}
UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User>{
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ly.mapper.UserMapper">
</mapper>
接下来配置application.yml文件
server:
port: 9000
spring:
datasource:
#引入druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
#spring整合dubbo
dubbo:
scan:
basePackages: com.ly #开启包扫描
application:
name: provider-user #必须添加服务名称
registry: #链接注册中心
address: zookeeper://192.168.180.160:2181
protocol:
name: dubbo
#注册中心信息存储文件夹的名称
port: 20880
#每个服务都有自己独立的端口号
#注意事项:服务的提供者可以有多个,但是必须服务名称必须一致.在服务内部.为了区分到底是哪个服务使用端口区分.
mybatis-plus:
type-aliases-package: com.ly.pojo #配置别名包路径
mapper-locations: classpath:/mybatis/mappers/*.xml #添加mapper映射文件
configuration:
map-underscore-to-camel-case: true #开启驼峰映射规则
@RestController
public class UserController {
/**
* check:true 检查是否有服务的提供者
* 先启动服务提供者,之后启动消费者
*/
@Reference(timeout=3000,check=true,loadbalance = "leastactive")
private UserService userService;
@RequestMapping("/findAll")
public List<User> findAll(){
return userService.findAll();
}
@RequestMapping("/saveUser/{name}/{age}/{sex}")
public String saveUser(User user) {
userService.saveUser(user);
return "用户入库成功!!!";
}
}
server:
port: 9001
dubbo:
scan:
basePackages: com.ly
application:
name: consumer-user
registry:
address: zookeeper://192.168.180.160:2181
由于消费者不需要连接数据库,我们需要在启动类加注解,使其忽略加载数据库连接
OK,我们实验一下。看一下消费者是否可以拿到数据
先 启动服务提供者,最后启动服务消费者。
可以看到数据已经拿到。
检查zookeeper启动正常
防火墙没有关闭
说明:如下报错表示服务器链接超时.
2020-05-04 10:58:33.725 ERROR 4984 --- [ restartedMain] org.apache.curator.ConnectionState : Connection timed out for connection string (192.168.180.160:2181) and timeout (5000) / elapsed (5032)
需要关闭防火墙
service iptables stop
测试负载均衡效果
说明:当启动多个服务的提供者时发现,dubbo框架自己实现了负载均衡的算法.
高可用测试:
负载均衡算法
1. 随机算法 RandomLoadBalance
@Reference(timeout=3000,check=true,loadbalance = "random")
@Reference(timeout=3000,check=true,loadbalance = "roundrobin")
@Reference(timeout=3000,check=true,loadbalance = "consistentHash")
@Reference(timeout=3000,check=true,loadbalance = "leastactive")
github地址::https://github.com/lmy1965673628/dubbo-demo.git
业务需求
添加dubbo的jar包
<!--引入dubbo配置 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
配置服务接口:在jt-common中创建:
public interface DubboUserService {
void insertUser(User user);
//实现数据库查询
String doLogin(User user);
}
配制服务提供者
在jt-sso的模块中修改yml文件
#spring整合dubbo
dubbo:
scan:
basePackages: com.ly #开启包扫描
application:
name: provider-user #必须添加服务名称
registry: #链接注册中心
address: zookeeper://192.168.180.160:2181?backup=192.168.180.160:2182,192.168.180.160:2183
protocol:
name: dubbo
#注册中心信息存储文件夹的名称
port: 20880
#每个服务都有自己独立的端口号
#注意事项:服务的提供者可以有多个,但是必须服务名称必须一致.在服务内部.为了区分到底是哪个服务使用端口区分.
添加dubbo实现类
登录时检验用户名是否正确,并将其放在redis缓存中。key值是随机生成串
@Service(timeout = 3000) //将对象交给dubbo管理
public class DubboUserServiceImpl implements DubboUserService {
@Autowired
private UserMapper userMapper;
}
编辑服务消费者
配置jt-web的yml文件
#添加dubbo配制
dubbo:
scan:
basePackages: com.ly
application:
name: consumer-web
registry:
address: zookeeper://192.168.180.160:2181
去看一下源码
OK,编辑消费者Controller,即jt-web中的controller
@Controller
@RequestMapping("/user")
public class UserController {
@Reference(timeout = 3000,check = true)
private DubboUserService userService;
/**
* 实现用户页面跳转
* http://www.jt.com/user/register.html
* http://www.jt.com/user/login.html
*/
@RequestMapping("/{moduleName}")
public String login(@PathVariable String moduleName) {
return "/"+moduleName;
}
/**
* 实现用户信息新增
*/
@RequestMapping("/doRegister")
@ResponseBody
public SysResult insertUser(User user) {
userService.insertUser(user);
return SysResult.success();
}
}
修改服务提供者的Service
@Service(timeout = 3000) //将对象交给dubbo管理
public class DubboUserServiceImpl implements DubboUserService {
@Autowired
private UserMapper userMapper;
@Override
public void insertUser(User user) {
//密码加密 注意加密和登录算法必须相同
String md5Pass =
DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass)
.setCreated(new Date())
.setUpdated(user.getCreated());
userMapper.insert(user);
}
}
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他联邦系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的
概括:用户只需要登录一次,就可以访问相互信任的系统
查看请求路径
源码查看
编辑UserController(jt-web)
@RequestMapping("/doLogin")
@ResponseBody
public SysResult doLogin(User user, HttpServletResponse response) {
//1.获取服务器加密秘钥
String ticket = userService.doLogin(user);
if(StringUtils.isEmpty(ticket)) {
return SysResult.fail();
}
//2.需要将数据保存到cookie中
Cookie cookie = new Cookie("JT_TICKET", ticket);
cookie.setMaxAge(7*24*3600);
//设定cookie的使用权限.
cookie.setPath("/");
//设定cookie共享!!!!
cookie.setDomain("jt.com");
//将cookie写入浏览器
response.addCookie(cookie);
return SysResult.success();
}
编辑DubboUserServiceImpl(jt-sso),也可以说编辑服务提供者
@Override
public String doLogin(User user) {
String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass);
QueryWrapper<User> queryWrapper = new QueryWrapper<User>(user);
User userDB = userMapper.selectOne(queryWrapper);
String key = null;
if(userDB!=null) {
//表示用户名密码正确 UUID
key = DigestUtils.md5DigestAsHex(UUID.randomUUID().toString().getBytes());
String userJSON = JsonUtil.toJSON(userDB);
jedisCluster.setex(key,7*24*3600, userJSON);
}
return key;
}
查看效果
输入正确的用户名和密码后,点击登录就会跳转到首页,我们看一下cookie,已经拿到值
用户信息回显
登陆成功后,头部并没有显示登陆信息,我们去找一下是否有请求链接,发现一个
查看源码,发现是个跨域请求
编辑JT-SSO Controller
/**
* 实现用户信息查询
* /user/query/" + _ticket
*/
@RequestMapping("/query/{ticket}")
public JSONPObject findUserByTicket(@PathVariable String ticket,String callback) {
String userJSON = jedisCluster.get(ticket);
JSONPObject jsonpObject = null;
if(StringUtils.isEmpty(userJSON)) {
jsonpObject = new JSONPObject(callback,SysResult.fail());
}else {
jsonpObject = new JSONPObject(callback,SysResult.success().setData(userJSON));
}
return jsonpObject;
}
当用户点击退出按钮时,首先应该删除Cookie信息.之后删除Redis信息.将页面重定向到系统首页.
@RequestMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
//1.获取cookie数据
Cookie[] cookies = request.getCookies();
//2.判断cookie是否有值
String ticket = null;
if(cookies.length>0) {
for (Cookie cookie : cookies) {
if("JT_TICKET".equals(cookie.getName())) {
ticket = cookie.getValue();
break;
}
}
}
//3.删除redis
if(!StringUtils.isEmpty(ticket)) {
jedisCluster.del(ticket);
}
/**
* 4.删除cookie
* setMaxAge
* >0 表示设定超时时间
* =0 表示立即删除cookie
* -1 当会话关闭后,删除cookie
*/
Cookie cookie = new Cookie("JT_TICKET","");
cookie.setMaxAge(0); //删除cookie
cookie.setPath("/");
cookie.setDomain("jt.com");
response.addCookie(cookie);
//重定向到系统首页
return "redirect:/";
}
重启服务,再次点击退出按钮,查看效果,可以看到cookie也删除,也可以看到redis缓存也删除
本节通过是用来Dubbo框架实现了单点登录操作,以及实现了js跨域解决其他页面请求。下一节我们继续讲解购物车的实现,使用拦截器实现权限控制,以及如何实现同线程内数据共享