Eureka实现注册中心
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。它是分布式系统中最核心最重要的理论。
分布式系统的CAP理论:理论首先把分布式系统中的三个特性进行了如下归纳:
l 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
l 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
l 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
ZooKeeper和Eureka对比
ZooKeeper基于CP,不保证高可用,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将无法获得数据。Eureka基于AP,能保证高可用,即使所有机器都挂了,也能拿到本地缓存的数据。作为注册中心,其实配置是不经常变动的,只有发版(发布新的版本)和机器出故障时会变。对于不经常变动的配置来说,CP是不合适的,而AP在遇到问题时可以用牺牲一致性来保证可用性,既返回旧数据,缓存数据。
所以理论上Eureka是更适合作注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是因为集群不够大,并且基本不会遇到用做注册中心的机器一半以上都挂了的情况。所以实际上也没什么大问题。
Eureka部署服务端、提供端、消费端(消费者整合负载均衡Ribbon,提供方整合Mybaits)
服务端:
搭建SpringBoot项目
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>eureka.server</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
application.yml
security: basic: enabled: true user: name: user password: password123 server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://user:password123@localhost:8761/eureka logging: level: root: INFO
RunAppEureka.java
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class RunAppEureka { public static void main(String[] args) { SpringApplication.run(RunAppEureka.class, args); } }
提供方1(加入整合SpringBoot整合Mybatis)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>eureka.provider</groupId> <artifactId>eureka-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <impala.jdbc.version>2.5.30</impala.jdbc.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
application.yml
server:
port: 7900
spring:
application:
name: provider-user
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=172.24.7.177)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=iovdb)))
username: foton
password: foton[zk]
mybatis:
typeAliasesPackage: cn.hz.pojo
mapperLocations: classpath:mappers/*.xml
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO
ProviderRunApp.java
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @MapperScan("cn.hz.mapper") public class ProviderRunApp { public static void main(String[] args) { SpringApplication.run(ProviderRunApp.class, args); } }
User.java
import java.io.Serializable; import java.util.Date; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; public class User implements Serializable { /** * */ private static final long serialVersionUID = 8693407369491314806L; /* * id NUMBER(19) not null, created TIMESTAMP(6), modified TIMESTAMP(6), * description VARCHAR2(255 CHAR), name VARCHAR2(255 CHAR), * cbm_mag_company_id NUMBER(19), category_id NUMBER(19), cbm_mag_user_id * NUMBER(19), layout CLOB */ private Long id; @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss" ) private Date created; @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss" ) private Date modified; private String description; private String name; private int sort; private Long companyId; private Long userId; private Long parentId; getter setter。。。 @Override public String toString() { return "User [id=" + id + ", created=" + created + ", modified=" + modified + ", description=" + description + ", name=" + name + ", sort=" + sort + ", companyid=" + companyId + ", userId=" + userId + ", parentId=" + parentId + "]"; }
UserMapper.java
import java.util.List; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import cn.hz.pojo.User; //注解和xml方式混合 public interface UserMapper { //调用xml方式 public List<User> find(); //调用注解方式 @Select("select id, created, modified, description, name, sort, cbm_mag_company_id as companyId, parent_id as parentId, cbm_mag_user_id as userId from IOV_DASH_CATEGORY where id=#{id}") public User get(@Param("id") Long id); }
UserService.java
import java.util.List; import cn.hz.pojo.User; public interface UserService { public List<User> find(); public User get(Long id); }
UserServiceImpl.java
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.hz.mapper.UserMapper; import cn.hz.pojo.User; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; public List<User> find() { return userMapper.find(); } public User get(Long id) { return userMapper.get(id); } }
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"> <!-- namespace命名空间,唯一特性 --> <mapper namespace="cn.hz.mapper.UserMapper"> <resultMap type="cn.hz.pojo.User" id="user"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="created" property="created" javaType="java.sql.Date"/> <result column="modified" property="modified" javaType="java.sql.Date"/> <result column="description" property="description" jdbcType="VARCHAR"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="sort" property="sort" jdbcType="INTEGER"/> <result column="companyId" property="cbm_mag_company_id" jdbcType="BIGINT"/> <result column="userId" property="cbm_mag_user_id" jdbcType="BIGINT"/> <result column="parentId" property="parent_id" jdbcType="BIGINT"/> </resultMap> <select id="find" resultType="user"> select id, created, modified, description, name, sort, cbm_mag_company_id as companyId, parent_id as parentId, cbm_mag_user_id as userId from IOV_DASH_CATEGORY </select> </mapper>
UserController.java (为整合MyBatis测试使用)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.hz.pojo.User; import cn.hz.service.UserService; @RestController @RequestMapping(value = "/user") public class UserController { @Autowired private UserService userSerivce; @GetMapping("/{name}") public String getName(@PathVariable String name) { return "hello:" + name; } @GetMapping("/list") public String list() { return userSerivce.find().toString(); } @GetMapping("/get/{id}") public String get(@PathVariable Long id) { User user = userSerivce.get(id); if (null == user) return null; else return userSerivce.get(id).toString(); } }
提供方2和提供方1创建基本相同,此处忽略
消费者:(整合Ribbon调用不同的提供方)
1.1.1 Ribbon
Feign是netflix开发的声明式、模板化的http客户端,在使用时就像调用本地(服务消费者自己)的方法一般,帮助我们更加优雅的调用服务提供者的API。Feign自身支持springMVC,还整合了Eureka、Ribbon,极大的简化了Feign的使用。就整合Euraka而言,只需和普通的服务配置Eureka server的信息即可。整合Ribbon,就意味着不再需要通过标注@LoadBalanced的实例化后的RestTemplate去调用服务提供者方法了。Feign只需通过简单的定义一个接口即可实现负载均衡。
和nginx不同,它是客户端侧负载均衡。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>eureka.client</groupId> <artifactId>eureka-client</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
application.yml
server:
port: 8010
spring:
application:
name: consumer-client
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO
ClientRunApp.java
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient @RibbonClient(name="provider-user", configuration=RibbonRuleConfig.class) public class ClientRunApp { @Bean @LoadBalanced //Ribbon负载均衡 public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ClientRunApp.class, args); } }
RibbonRuleConfig.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; /** * * 自定义Ribbon配置 * 规定:这个类不能再@ComponentScan和@SpringBootApplication本包和子包下,否则引起@RibbonClients扫描冲突 * 注意:随机第一次打断点进入,之后多次刷新就不进入,可能由于本地缓存原因 */ @Configuration public class RibbonRuleConfig { @Bean public IRule ribbonRule(){ return new RandomRule(); } }
UserController.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/user/{id}") @ResponseBody public String getName(@PathVariable String id) { //String url = "http://localhost:7900/user/"+ name; //provider-user就是Eureka中提供服务 String url = "http://provider-user/user/get/"+id; return "client:" + this.restTemplate.getForObject(url, String.class); //return url; } }