使用SpringCloud实战微服务

【故障排查】拒绝连接: /127.0.0.1:9848 Server check fail, please check server 127.0.0.1 ,port 9848 is available 【代码】【故障排查】拒绝连接: /127.0.0.1:9848 Server check fail, please check server 127.0.0.1 ,port 9848 is available。 阅读详情
AI摘要

本文详细介绍了微服务架构的概念、特点及其面临的挑战,并深入探讨了Spring Cloud在微服务架构中的应用,包括服务注册与发现、负载均衡、容错处理等关键技术。

一微服务架构概述

1.1 微服务特性以及优点

  • 每个服务可以独立运行在自己的进程里
  • 一系列独立运行的微服务(goods,order,pay,user,search…)共同构建了整个系统
  • 每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如用户管理,商品管理微服务
  • 微服务之间通过一些轻量级的通信机制进行通讯,例如通过Restful API进行调用
  • 技术栈不受限:可以使用不同的开发语言和数据存储技术
  • 全自动的部署机制
  • 按需伸缩:根据需求和应用场景,实现细粒度的水平扩展

1.2 微服务带来的挑战

  • 运维要求较高
  • 分布式的复杂性
  • 接口调整成本较高

1.3 微服务设计原则

  • 单一职责原则
  • 服务自治原则
  • 轻量级通讯机制
  • 微服务粒度

1.4 微服务开发框架

  • SpringCloud:众多组件构造完善的分布式系统
  • Dubbo/Dubbox:关注服务治理
  • Dropwizard:关注单个微服务开发

二 SpringCloud概述与开发环境

2.1 SpringCloud概述

SpringCloud是基于SpringBoot之上的用来快速构建微服务系统的工具集,拥有功能完善的轻量级微服务组件,例如服务治理(Eureka),声明式REST调用(Feign),客户端负载均衡(Ribbon),服务容错(Hystrix),服务网关(Zuul)以及服务配置(Spring Cloud Config),服务跟踪(Sleuth)等等。

官网链接:http://projects.spring.io/spring-cloud/
目前主流的版本为SpringBoot1.4.5.RELEAE和SpringCloudCamden.SR7,
Maven pom配置如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.5.RELEASE</version>
</parent>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Camden.SR7</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId></groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId></groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
</dependencies>

2.2 开发环境

MacOS10.12+JDK8u131+IntelliJ IDEA2017.1.4

Tomcat8.5+Maven3.3.9+Git2.12+Firefox54

Spring4.3.9.RELEASE+SpringBoot1.4.5.RELEASE+SpringCloud Camden.SR7

三 工程机器模块说明

3.1 工程说明

工程首先自定义了Maven父工程,其中定义如下的公共组件:

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <!-- 自定义工程的maven坐标-->
    <groupId>com.ekeyfund.springcloud</groupId>
    <artifactId>springcloud-parent</artifactId>
   <version>2.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!-- 基于SpringBoot 1.4.5.RELEASE-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.5.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>
        <spring-cloud.version>Camden.SR7</spring-cloud.version>
        <springcloud-parent.version>2.0.0-SNAPSHOT</springcloud-parent.version>

        <druid.version>1.0.31</druid.version>
        <jackson.version>2.8.8</jackson.version>
        <commons-lang3.version>3.5</commons-lang3.version>
        <ehcache.version>3.1.4</ehcache.version>
        <hibernate.version>5.0.12.Final</hibernate.version>
        <servlet-api.version>3.1.0</servlet-api.version>
        <commons-collection4.version>4.1</commons-collection4.version>

        <springframework.oxm.version>4.3.9.RELEASE</springframework.oxm.version>
    </properties>


    <!-- 引入SpringCloud微服务常用组件-->
    <modules>
        <module>springcloud-eureka-server</module>
        <module>springcloud-eureka-server-ha</module>
        <module>springcloud-provider-user-service</module>
        <module>springcloud-consumer-h5-ribbon-hystrix</module>
        <module>springcloud-consumer-h5-feign</module>
        <module>springcloud-api-gateway</module>
        <module>springcloud-consumer-h5</module>
        <module>springcloud-config-server</module>
    </modules>



    <dependencies>

        <!-- 服务发现组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

        <!-- 应用监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 应用测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>


        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet-api.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>${commons-collection4.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>${ehcache.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${springframework.oxm.version}</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

3.2 模块说明

模块则借助IntelliJ IDEA结合Spring Initializer自动生成工程结构,主要模块和说明如下:

模块名称模块说明访问地址
springcloud-eureka-server分布式服务注册中心(单点)http://127.0.0.1:9999
springcloud-eureka-server-ha分布式服务注册中心(高可用版本)http://127.0.0.1:9998 http://127.0.0.1:9997
springcloud-provider-user-service用户服务提供者http://127.0.0.1:9996/list http://127.0.0.1:9995/list
springcloud-consumer-h5用户服务调用者,采用原始的RestTemplate调用http://127.0.0.1:9991/user/get/4
springcloud-consumer-h5-ribbon-hystrix用户服务调用者,采用ribbon做客户端负载均衡http://127.0.0.1:9994/springcloud-provider-user-service
springcloud-consumer-h5-feignfeign声明式服务调用者http://127.0.0.1:9993/list
springcloud-gateway网关服务http://127.0.0.1:9992/api-a/list
springcloud-config-server配置中心待定

四 使用SpringBoot实现服务提供者

所属maven模块:springcloud-provider-user-service
基于SpringBoot的Web和JPA模块实现Restful API的常用方法

4.1 entity

主要包含User,Role,Department三个实体

Role.java

package com.ekeyfund.springcloud.entity;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Role Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:36
 */
@Entity
@Table(name = "springboot_role")
public class Role  implements Serializable{


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long id;

    @Column(name = "role_name")
    private String name;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return new org.apache.commons.lang3.builder.ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .toString();
    }
}

Department.java

package com.ekeyfund.springcloud.entity;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Department Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:31
 */
@Entity
@Table(name = "springboot_department")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Department implements Serializable {


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "department_id")
    private Long id;


    @Column(name = "department_name")
    private String name;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .toString();
    }
}

User.java

package com.ekeyfund.springcloud.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * User Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:32
 */
@Entity
@Table(name = "springboot_user")
public class User  implements Serializable{

    @Id
    @Column(name = "user_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name")
    private String name;


    @Column(name = "user_password")
    private String password;


    @Column(name = "user_create_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;


    @ManyToOne
    @JoinColumn(name = "department_id")
    @JsonBackReference
    private Department department;



    @ManyToMany(cascade = {},fetch = FetchType.EAGER)
    @JoinTable(name = "springboot_user_role",joinColumns = {@JoinColumn(name="user_id")},
                inverseJoinColumns = {@JoinColumn(name = "role_id")}
    )
    private List<Role> roleList;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .append("password", password)
                .append("createDate", createDate)
                .append("department", department)
                .append("roleList", roleList)
                .toString();
    }
}

4.2 数据访问Repository

主要包含UserRepository和DepartmentRepository

DepartmentRepository.java

package com.ekeyfund.springcloud.repository;

import com.ekeyfund.springcloud.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * Created by tony on 2017/6/19.
 */
@Repository
public interface DepartmentRepository extends JpaRepository<Department,Long> {
}

UserRepoistory.java

package com.ekeyfund.springcloud.repository;

import com.ekeyfund.springcloud.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * User Repository
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:54
 */
@Repository
public interface UserRepository  extends JpaRepository<User,Long>{


    /**
     *  and
     * @param id
     * @param name
     * @return
     */
    User findByIdAndName(Long id, String name);


    User findByNameAndPassword(String name, String password);

    /**
     *  or
     * @param id
     * @param name
     * @return
     */
    User findByIdOrName(Long id, String name);


    /**
     * between
     * @param start
     * @param end
     * @return
     */
    List<User> findByCreateDateBetween(Date start, Date end);


    /**
     * lessThan
     * @param start
     * @return
     */
    List<User> getByCreateDateLessThan(Date start);

    /**
     * Greater Than
     * @param start
     * @return
     */
    List<User> findByCreateDateGreaterThan(Date start);


    /**
     * is null
     * @return
     */
    List<User> findByNameIsNull();


    /**
     * in
     * @param nameList
     * @return
     */
    List<User> findByNameIn(Collection<String> nameList);

}

4.3 业务逻辑Service

主要包含UserService,DepartmentService及其实现

UserService.java

package com.ekeyfund.springcloud.service;


import com.ekeyfund.springcloud.entity.User;

import java.util.List;

/**
 * Created by tony on 2017/6/19.
 */
public interface UserService {


    /**
     * 登录
     * @param name
     * @param password
     * @return
     */
    public User login(String name, String password);


    /**
     * 注册
     * @param user
     * @return
     */
    public User register(User user);


    /**
     * 注销
     * @param user
     * @return
     */
    void writeOff(User user);

    /**
     * 当前用户是否已经存在
     * @param user
     * @return
     */
    boolean isExists(User user);

     List<User> getAllUser();

     User getUserById(Long id);

}

UserServiceImpl.java

package com.ekeyfund.springcloud.service.impl;


import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.repository.UserRepository;
import com.ekeyfund.springcloud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

/**
 * User Service Impl
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午3:34
 */
@Service
@Transactional
public class UserServiceImpl implements UserService {



    @Autowired
    private UserRepository userRepository;


    @Override
    public User login(String name, String password) {
        return userRepository.findByNameAndPassword(name,password);
    }

    @Override
    public User register(User user) {
        return userRepository.save(user);
    }

    @Override
    public void writeOff(User user) {
         userRepository.delete(user);
    }

    @Override
    public boolean isExists(User user) {
        return userRepository.findOne(user.getId())!=null?true:false;
    }

    @Override
    public List<User> getAllUser() {
        return userRepository.findAll();
    }

    @Override
    public User getUserById(Long id) {
        return userRepository.findOne(id);
    }
}

DepartmentService.java

package com.ekeyfund.springcloud.service;


import com.ekeyfund.springcloud.entity.Department;

/**
 * Created by tony on 2017/6/19.
 */
public interface DepartmentService {

     Department saveDepartment(Department department);

     Department getDepartmentById(Long id);
}

DepartmentServiceImpl.java

package com.ekeyfund.springcloud.service.impl;


import com.ekeyfund.springcloud.entity.Department;
import com.ekeyfund.springcloud.repository.DepartmentRepository;
import com.ekeyfund.springcloud.service.DepartmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

/**
 * Department Impl
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午3:12
 */
@Transactional
@Service
public class DepartmentImpl implements DepartmentService {


    @Autowired
    private DepartmentRepository departmentRepository;


    @Override
    public Department saveDepartment(Department department) {
        return departmentRepository.save(department);
    }

    @Override
    public Department getDepartmentById(Long id) {
        return departmentRepository.findOne(id);
    }
}

4.4 Controller层

主要包含提供User完整的Restful API 的UserController

UserController.java

package com.ekeyfund.springcloud.controller;

import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.service.UserService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;

/**
 * UserController
 * Restful API
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午11:24
 */
@RestController
public class UserController {



    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);


    @Autowired
    private DiscoveryClient discoveryClient;


    @Autowired
    private UserService userService;


    @GetMapping(value = "/list")
   public List<User> list(){

        ServiceInstance instance=discoveryClient.getLocalServiceInstance();
        LOGGER.info("call user/list service  host is  "+instance.getHost()+"service_id is "+instance.getServiceId());
        return userService.getAllUser();
   }

   @GetMapping(value = "/login")
   public User login( @RequestParam String name,@RequestParam String password){

       User user=userService.login(name,password);
       return user;
   }

   @PostMapping("/register")
   public String register(@ModelAttribute User user){
       User result =userService.register(user);
       return result!=null?"success":"fail";
   }

   @GetMapping("/get/{id}")
   public User get(@PathVariable Long id){

       return userService.getUserById(id);
   }

   @PutMapping("/update/{id}")
   public String update(@PathVariable Long id,@ModelAttribute User user){

       User updatedUser =userService.getUserById(id);
       updatedUser.setName(user.getName());
       updatedUser.setPassword(user.getPassword());
       updatedUser.setCreateDate(new Date());
       User result= userService.register(updatedUser);
       return result!=null?"success":"fail";

   }


   @DeleteMapping("/delete/{id}")
   public String delete(@PathVariable Long id){

       User user =new User();
       user.setId(id);
       userService.writeOff(user);

       return "success";
   }


}

4.5 Configuration

主要包含数据源Druid和JPA的配置

DruidConfiguation.java

package com.ekeyfund.springcloud.configuration;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;

/**
 * Druid Configuration
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午5:48
 */
public class DruidConfiguration {
    @Bean
    public ServletRegistrationBean statViewServle(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        //白名单:
        servletRegistrationBean.addInitParameter("allow","192.168.1.218,127.0.0.1");
        //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的即提示:Sorry, you are not permitted to view this page.
        servletRegistrationBean.addInitParameter("deny","192.168.1.100");
        //登录查看信息的账号密码.
        servletRegistrationBean.addInitParameter("loginUsername","druid");
        servletRegistrationBean.addInitParameter("loginPassword","12345678");
        //是否能够重置数据.
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean statFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
        //添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

JPAPersistenceConfiguration

package com.ekeyfund.springcloud.configuration;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;

/**
 * JPA Persistence Configuration
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-上午11:26
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true) //启用JPA的事务管理
@EnableJpaRepositories(basePackages = "com.ekeyfund.springcloud.repository" )//启用JPA资源库并指定资源库接口位置
@EntityScan(basePackages = "com.ekeyfund.springcloud.entity")//指定实体的位置
public class JPAPersistenceConfiguration {



    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JPAPersistenceConfiguration.class);


    /*******************数据库和连接池配置信息,读取application.properties文件的属性值****************************/
    @Value("${spring.datasource.driver-class-name}")
    private String driverClass;

    @Value("${spring.datasource.username}")
    private String userName;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.maxWait}")
    private long maxWait;

    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private long timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private long minEvictableIdleTimeMillis;


    @Value("${spring.datasource.filters}")
    private String filters;


    @Value("${spring.datasource.connectionProperties}")
    private String connectionProperties;


    @Bean(name = "druidDataSource",initMethod = "init",destroyMethod = "close")
    public DataSource dataSource(){
        DruidDataSource druidDataSource =new DruidDataSource();
        druidDataSource.setDriverClassName(driverClass);
        druidDataSource.setUsername(userName);
        druidDataSource.setPassword(password);
        druidDataSource.setUrl(url);

        druidDataSource.setInitialSize(initialSize);
        druidDataSource.setMinIdle(minIdle);
        druidDataSource.setMaxActive(maxActive);
        druidDataSource.setMaxWait(maxWait);
        druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        druidDataSource.setConnectionProperties(connectionProperties);
        try {
            druidDataSource.setFilters(filters);
        } catch (SQLException e) {
            LOGGER.error("build datasoure exception ",e.getMessage());
        }

        return druidDataSource;
    }



    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource druidDataSource){
        LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =new LocalContainerEntityManagerFactoryBean();
        localContainerEntityManagerFactoryBean.setDataSource(druidDataSource);
        localContainerEntityManagerFactoryBean.setPackagesToScan("com.ekeyfund.springcloud.entity");
        localContainerEntityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
        localContainerEntityManagerFactoryBean.setJpaDialect(new HibernateJpaDialect());
        localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter(){
            {
                setDatabase(org.springframework.orm.jpa.vendor.Database.MYSQL);
                setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
            }
        });
        return localContainerEntityManagerFactoryBean;
    }


    @Bean
    public PlatformTransactionManager transactionManager(DataSource druidDataSource, EntityManagerFactory entityManagerFactory){
        JpaTransactionManager jpaTransactionManager=new JpaTransactionManager();
        jpaTransactionManager.setDataSource(druidDataSource);
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
        return jpaTransactionManager;
    }

    @Bean
    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
        return new PersistenceExceptionTranslationPostProcessor();
    }



    protected Properties buildHibernateProperties(){
        Properties hibernateProperties =new Properties();
        hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        hibernateProperties.setProperty("hibernate.hbm2ddl.auto","update");
        hibernateProperties.setProperty("hibernate.show_sql", "false");
        hibernateProperties.setProperty("hibernate.use_sql_comments", "false");
        hibernateProperties.setProperty("hibernate.format_sql", "true");
        hibernateProperties.setProperty("hibernate.generate_statistics", "false");
        hibernateProperties.setProperty("javax.persistence.validation.mode", "none");
        //Audit History flags
        hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", "true");
        hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", "true");
        hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "true");
        hibernateProperties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
        hibernateProperties.setProperty("hibernate.cache.use_query_cache", "true");


        return hibernateProperties;
    }

}

4.6 应用配置

主要包含springboot的application.properties,logback的logback-spring.xml以及缓存框架的ehcache.xml

application.propeties


##DataSource Config
##\u6570\u636e\u5e93\u8fde\u63a5\u6c60\u4fe1\u606f\u914d\u7f6e
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=guanglei
# \u4e0b\u9762\u4e3a\u8fde\u63a5\u6c60\u7684\u8865\u5145\u8bbe\u7f6e\uff0c\u5e94\u7528\u5230\u4e0a\u9762\u6240\u6709\u6570\u636e\u6e90\u4e2d
# \u521d\u59cb\u5316\u5927\u5c0f\uff0c\u6700\u5c0f\uff0c\u6700\u5927
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# \u914d\u7f6e\u83b7\u53d6\u8fde\u63a5\u7b49\u5f85\u8d85\u65f6\u7684\u65f6\u95f4
spring.datasource.maxWait=60000
# \u914d\u7f6e\u95f4\u9694\u591a\u4e45\u624d\u8fdb\u884c\u4e00\u6b21\u68c0\u6d4b\uff0c\u68c0\u6d4b\u9700\u8981\u5173\u95ed\u7684\u7a7a\u95f2\u8fde\u63a5\uff0c\u5355\u4f4d\u662f\u6beb\u79d2
spring.datasource.timeBetweenEvictionRunsMillis=60000
# \u914d\u7f6e\u4e00\u4e2a\u8fde\u63a5\u5728\u6c60\u4e2d\u6700\u5c0f\u751f\u5b58\u7684\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u6beb\u79d2
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# \u6253\u5f00PSCache\uff0c\u5e76\u4e14\u6307\u5b9a\u6bcf\u4e2a\u8fde\u63a5\u4e0aPSCache\u7684\u5927\u5c0f
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# \u914d\u7f6e\u76d1\u63a7\u7edf\u8ba1\u62e6\u622a\u7684filters\uff0c\u53bb\u6389\u540e\u76d1\u63a7\u754c\u9762sql\u65e0\u6cd5\u7edf\u8ba1\uff0c'wall'\u7528\u4e8e\u9632\u706b\u5899
spring.datasource.filters=stat,wall,log4j
# \u901a\u8fc7connectProperties\u5c5e\u6027\u6765\u6253\u5f00mergeSql\u529f\u80fd\uff1b\u6162SQL\u8bb0\u5f55
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

# \u5408\u5e76\u591a\u4e2aDruidDataSource\u7684\u76d1\u63a7\u6570\u636e
#spring.datasource.useGlobalDataSourceStat=true

# druid \u8bbf\u95ee\u5730\u5740 http://host:port/druid/index.html

##Log Config
logging.config=classpath:logback-spring.xml

## SpringData JPA Config
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true

server.port=9996

ehcache.xml

<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <!--
        指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
    -->
    <diskStore path="tempDirectory"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <!--
        设置缓存的默认数据过期策略
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
    />

    <!--
        设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
        缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
        如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
        Hibernate 在不同的缓存区域保存不同的类/集合。
         对于类而言,区域的名称是类名。如:com.ekeyfund.springboot.jpa.entity.User
         对于集合而言,区域的名称是类名加属性名。如com.ekeyfund.springboot.jpa.entity.User.roleList
    -->
    <!--
        name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
     maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目

     eternal: 设置对象是否为永久的, true表示永不过期,
     此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
     timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。
     当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
     timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
     如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值

     overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
    -->
    <cache name="com.ekeyfund.springcloud.entity.Department"
           maxElementsInMemory="1"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           overflowToDisk="true"
    />

    <cache name="com.ekeyfund.springcloud.entity.User"
           maxElementsInMemory="1000"
           eternal="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="false"
    />

    <cache name="com.ekeyfund.springcloud.entity.Role"
           maxElementsInMemory="1000"
           eternal="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="false"
    />

</ehcache>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
    <!-- 项目的appid -->
    <property name="APP_ID" value="SpringCloud-Provider-User-Service"/>
    <property name="LOG_PATH" value="logs"></property>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="FILE_LOG"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
        </filter>
        <file>${LOG_PATH}/${APP_ID}/access.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_ID}/access.log.%d{yyyy-MM-dd}.zip
            </fileNamePattern>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE_DEBUG"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/${APP_ID}/access_debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_ID}/access_debug.log.%d{yyyy-MM-dd}.zip
            </fileNamePattern>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE_INFO"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/${APP_ID}/access_info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_ID}/access_info.log.%d{yyyy-MM-dd}.zip
            </fileNamePattern>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE_WARN"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/${APP_ID}/access_warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_ID}/access_warn.log.%d{yyyy-MM-dd}.zip
            </fileNamePattern>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE_ERROR"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/${APP_ID}/access_error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_ID}/access_error.log.%d{yyyy-MM-dd}.zip
            </fileNamePattern>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>


    <appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_LOG"/>
    </appender>
    <appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_LOG"/>
    </appender>
    <appender name="ASYNC_LOG_DEBUG" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_DEBUG"/>
    </appender>
    <appender name="ASYNC_LOG_INFO" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_INFO"/>
    </appender>
    <appender name="ASYNC_LOG_WARN" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_WARN"/>
    </appender>
    <appender name="ASYNC_LOG_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <appender-ref ref="FILE_ERROR"/>
    </appender>
    <root level="INFO">
        <!-- appender referenced after it is defined -->
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ASYNC_LOG"/>
        <appender-ref ref="ASYNC_LOG_DEBUG"/>
        <appender-ref ref="ASYNC_LOG_INFO"/>
        <appender-ref ref="ASYNC_LOG_WARN"/>
        <appender-ref ref="ASYNC_LOG_ERROR"/>
    </root>
    <logger name="org.springframework" level="INFO"/>
</configuration>

4.7 pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<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>

    <artifactId>springcloud-provider-user-service</artifactId>
    <packaging>jar</packaging>
    <name>springcloud-user-service</name>
    <description>SpringCloud User Service Application</description>

    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudUserServiceMasterApplication</start-class>

    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>



    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

4.8 启动类

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

//@EnableDiscoveryClient//激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例)
@SpringBootApplication
public class SpringcloudUserServiceMasterApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudUserServiceMasterApplication.class, args);
    }
}

4.9 测试用例

package com.ekeyfund.springcloud;

import com.ekeyfund.springcloud.entity.Department;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.service.DepartmentService;
import com.ekeyfund.springcloud.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest
@ComponentScan("com.ekeyfund.springcloud")
public class SpringcloudUserServiceApplicationTests {


    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(SpringcloudUserServiceApplicationTests.class);


    @Test
    public void contextLoads() {
    }



    @Autowired
    private DepartmentService departmentService;


    @Autowired
    private UserService userService;


    @Test
    public void testDepartmentService(){

        Department department=new Department();
        department.setName("dev");
        Department result =departmentService.saveDepartment(department);

        LOGGER.info("add result "+result);


        Long id =1L;
        result =departmentService.getDepartmentById(id);
        LOGGER.info("get department "+result);
    }


    @Test
    public void testUserRegister()throws Exception{
        User user =new User();
        user.setName("tony");
        user.setPassword("666666");
        user.setCreateDate(new Date());
        Department department=departmentService.getDepartmentById(1L);
        user.setDepartment(department);
        User result =userService.register(user);
        LOGGER.info("register result "+result);

    }


    @Test
    public void testWriteOff()throws Exception{
        User user =new User();
        user.setName("tony");
        user.setPassword("666666");
        userService.writeOff(user);

    }



    @Test
    public void testUserLogin()throws Exception{
        User user =new User();
        user.setName("tony");
        user.setPassword("666666");
        User result =userService.login(user.getName(),user.getPassword());
        LOGGER.info("login  "+result);

    }

    @Test
    public void testUserIsExist()throws Exception{

        User user =new User();
        user.setId(4L);
        boolean result =userService.isExists(user);
        LOGGER.info("isExist  "+result);

    }

}

五 使用SpringBoot实现服务消费者

所属maven模块:springcloud-consumer-h5

5.1 entity

主要包含User,Department,Role
Role.java

package com.ekeyfund.springcloud.entity;

/**
 * Role Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:36
 */

public class Role {



    private Long id;

    private String name;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return new org.apache.commons.lang3.builder.ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .toString();
    }
}

Department.java

package com.ekeyfund.springcloud.entity;

import org.apache.commons.lang3.builder.ToStringBuilder;

/**
 * Department Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:31
 */

public class Department {



    private Long id;


    private String name;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .toString();
    }
}

User.java

package com.ekeyfund.springcloud.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;
import java.util.List;

/**
 * User Entity
 *
 * @author Liuguanglei liuguanglei@ekeyfund.com
 * @create 2017-06-下午2:32
 */

public class User {


    private Long id;

    private String name;


    private String password;


    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;



    @JsonBackReference
    private Department department;




    private List<Role> roleList;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .append("password", password)
                .append("createDate", createDate)
                .append("department", department)
                .append("roleList", roleList)
                .toString();
    }
}

5.2 Configuraiton

主要包含Spring MVC相关配置

MVCConfiguration.java

package com.ekeyfund.springcloud.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableWebMvc
public class MVCConfiguration extends WebMvcConfigurerAdapter{




        /**
         * 
         * @return
         */
        @Bean
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
          RequestMappingHandlerAdapter requestMappingHandlerAdapter=new RequestMappingHandlerAdapter();
          List<HttpMessageConverter<?>> messageConverters =new ArrayList<>();
          messageConverters.add(mappingJackson2HttpMessageConverter());
          requestMappingHandlerAdapter.setMessageConverters(messageConverters);
          return requestMappingHandlerAdapter;
      }




        //@Bean
        public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
            MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =new MappingJackson2HttpMessageConverter();
            List<MediaType> supportedMediaTypes =new ArrayList<>();
            supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
            mappingJackson2HttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
            return  mappingJackson2HttpMessageConverter;
        }


        @Bean
        public RestTemplate restTemplate(){
            RestTemplate restTemplate =new RestTemplate();

            return restTemplate;
        }


}

5.3 Controller

主要包含UserController

UserController.java

package com.ekeyfund.springcloud.controller;

import com.ekeyfund.springcloud.entity.User;
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.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * UserController
 *
 * @author tony 18601767221@163.com
 * @create 2017-06-27-下午11:42
 * @see
 * @since JDK1.8u133
 */
@RestController
public class UserController {


    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("user/get/{id}")
    public User get(@PathVariable Long id){

        return this.restTemplate.getForObject("http://127.0.0.1:9996/get/{1}",User.class,id);
    }

}

5.4 应用配置

主要包含springboot的application.properties

application.properties

server.port=9991
server.tomcat.uri-encoding=UTF-8

5.5 启动类

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringcloudConsumerH5Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerH5Application.class, args);
    }
}

5.6 测试服务调用

9991端口的服务消费者会调用9996端口的服务提供者接口。
通常在实际的开发中会遇到类似的情景:例如PC/H5/Android/IOS会去调用User模块的登录服务。

RestTemplate Call

弊端:因为程序调用的IP和端口采用了硬编码,而IP和端口可能变更,因此难以维护。
而当服务提供者宕机后服务会不可用,后面会在系统中引入服务注册、发现组件,该组件主要是维护了一个服务注册表,服务提供者和消费者将服务注册到该注册表中,而服务注册和发现组件会通过发送心跳来判断服务是否可用。

六 使用Eureka实现服务发现组件

所属maven模块:springcloud-eureka-server

1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.ekeyfund.springcloud</groupId>
    <artifactId>springcloud-eureka-server</artifactId>
    <packaging>jar</packaging>

    <name>springcloud-eureka-server</name>
    <description>SpringCloud Eureka Server </description>

    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>

    </parent>



    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudEurekaServerApplication</start-class>
    </properties>



</project>

2 编写启动类,在启动类上添加@EnableEurekaServer注解,声明这是一个Eureka Server

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * 单点eureka server
 */
@EnableEurekaServer //启用Eureka Server
@SpringBootApplication
public class SpringcloudEurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
    }
}

3 在应用配置文件application.properties添加如下内容:

spring.application.name=springcloud-cureka-server
server.port=9999
eureka.instance.hostname=127.0.0.1
#定义注册中心的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

#不向注册中心注册自己
eureka.client.register-with-eureka=false
#注册中心的职责就是去维护服务实例,不需要去检索服务
eureka.client.fetch-registry=false

eureka.instance.prefer-ip-address=true

4 测试访问Eureka Server

运行SpringcloudEurekaServerApplication,在浏览器输入 http://127.0.0.1:9999即可以访问Eureka Server

Eureka Server

七 服务注册

所属maven模块 springcloud-provider-user-service

1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <artifactId>springcloud-provider-user-service</artifactId>
    <packaging>jar</packaging>
    <name>springcloud-user-service</name>
    <description>SpringCloud User Service Application</description>

    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudUserServiceMasterApplication</start-class>

    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <!-- eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2 在application.properties文件中添加以下配置:

#####Eureka Client Config#######

#设置服务名称
spring.application.name=springcloud-provider-user-service
#eureka 单实例配置
#eureka.client.service-url.defaultZone=http://127.0.0.1:9999/eureka
eureka.instance.prefer-ip-address=true

其中spring.application.name是指定注册到Eureka Server上的应用名称
eureka.instance.prefer-ip-address表示将自己的IP注册到Eureka Server

3 编写启动类,在启动类上添加@EnableDiscoveryClient注解,声明这是一个Eureka Client

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient//激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例)
@SpringBootApplication
public class SpringcloudUserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudUserServiceMasterApplication.class, args);
    }
}

4 测试服务注册
首先运行SpringcloudEurekaServerApplication,启动Eureka Server,然后启动SpringcloudUserServiceApplication,启动Eureka Client。
访问Eureka Server,效果如下图:
Eureka Server Register

八 Eureka Server 高可用实现

在生产环境中,如果Eureka Server所在的机器发生宕机,那么服务发现组件将会变得不可用,因此需要实现高可用。Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,实例之间会彼此增量同步信息,从而保持所有节点的数据一致。

所属maven模块springcloud-eureka-server-ha

该模块使用了springboot的多环境配置特性来激活两个Eureka Server,因此准备了application.properties,application-master.properties和application-slave.properties三个配置文件

application.properties

spring.profiles.active=master
##使用ip地址的形式定义注册中心的地址
eureka.instance.prefer-ip-address=true
#禁用自我保护模式
#eureka.server.enable-self-preservation=false

###eureka server 启用安全验证
#security.user.name=tony
#security.user.password=666666
#security.basic.enabled=true

##服务续约任务的调用间隔时间默认为30s
#eureka.instance.lease-renewal-interval-in-seconds=3
###定义服务失效的时间默认是90s
#eureka.instance.lease-expiration-duration-in-seconds=540

application-master.properties

spring.application.name=springcloud-eureka-server-ha
server.port=9998

eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:9997/eureka/

application-slave.properties

spring.application.name=springcloud-eureka-server-ha
server.port=9997

eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:9998/eureka/

同时准备两个启动类用来启动两个Eureka Server

SpringcloudEurekaMasterServerApplication.java

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEurekaMasterServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEurekaMasterServerApplication.class, args);
    }
}

SpringcloudEurekaSlaveServiceApplication.java

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEurekaSlaveServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEurekaSlaveServiceApplication.class, args);
    }
}

分别运行SpringcloudEurekaMasterServerApplication和SpringcloudEurekaSlaveServiceApplication后会发现两个注册中心已经完成相互注册

9998端口的Eureka Server
9998端口的Eureka Server

9997端口的Eureka Server
9997端口的Eureka Server

所属maven模块 springcloud-provider-user-service

服务提供者的服务注册只需要改变application.properties的注册地址即可

#####Eureka Client Config#######

#设置服务名称
spring.application.name=springcloud-provider-user-service
#指定服务注册中心的地址 ###高可用改造后可以加上多个注册中心的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:9998/eureka/,http://127.0.0.1:9997/eureka/

eureka.instance.prefer-ip-address=true

#####Eureka Client Config#######

#指定服务提供者的端口
server.port=9996

刷新Eureka Server,查看高可用的Eureka Server

user服务同时注册在两个Eureka Server

eureka server ha

eureka server ha

九 Eureka Server实现安全验证

默认情况下Eureka Server允许匿名访问,这里创建一个需要登录后才能访问Eureka Server

在pom.xml中添加安全依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

然后在application.properties中添加http basic验证配置

##eureka server 启用安全验证
security.user.name=tony
security.user.password=666666
security.basic.enabled=true

运行SpringcloudEurekaMasterServerApplication或者SpringcloudEurekaSlaveServerApplication后,访问Eureka Server需要提供用户名和密码才能访问
eureka security

将服务注册到需要认证的Eureka Server,只需要将eureka.client.serviceUrl.defaultZone配置为http://user:password@127.0.0.1:9998/eureka/,http://user:password@127.0.0.1:9997/eureka/即可

例如 高可用Eureka Server的注册地址变更为

master

spring.application.name=springcloud-eureka-server-ha
server.port=9998

eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://tony:666666@${eureka.instance.hostname}:9997/eureka/

slave

spring.application.name=springcloud-eureka-server-ha
server.port=9997

eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://tony:666666@${eureka.instance.hostname}:9998/eureka/

十 使用Ribbon实现客户端负载均衡

所属maven模块:springcloud-consumer-h5-ribbon-hystrix

Ribbon是Netflix发布的负载均衡器,有助于控制HTTP和TCP客户端的行为,为Ribbon配置服务提供者地址列表后,Ribbon可以基于负载均衡算法(例如轮询、随机)自动的帮助消费者去请求。
在SpringCloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。

10.1 为服务消费者整合Ribbon

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.ekeyfund.springcloud</groupId>
    <artifactId>springcloud-consumer-h5-ribbon-hystrix</artifactId>
    <packaging>jar</packaging>

    <name>springcloud-consumer-h5-ribbon-hystrix</name>
    <description>Spring Cloud H5 Appliaction</description>


    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>

    </parent>

    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudH5RibbonHystrixApplication</start-class>

    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.8</version>
        </dependency>

    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

MVCConfiguration.java

package com.ekeyfund.springcloud.configuration;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * MVCConfiguration
 *
 * @author Liuguanglei 18601767221@163.com
 * @create 2017-06-下午1:01
 */
@Configuration
@EnableWebMvc
public class MVCConfiguration extends WebMvcConfigurerAdapter{




    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){

        return new MappingJackson2HttpMessageConverter();
    }


    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter){
        RequestMappingHandlerAdapter requestMappingHandlerAdapter=new RequestMappingHandlerAdapter();
        List<HttpMessageConverter<?>> messageConverters=new ArrayList<>();
        messageConverters.add(mappingJackson2HttpMessageConverter);
        requestMappingHandlerAdapter.setMessageConverters(messageConverters);
        return requestMappingHandlerAdapter;
    }


    @Bean
    @LoadBalanced//开启客户端负载均衡
    public RestTemplate restTemplate(){

     return new RestTemplate();
    }
}

相比之前的配置主要是在RestTemplate中添加了@LoadBalanced注解即可整合Ribbon实现客户端的负载均衡。

UserController.java

package com.ekeyfund.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.ekeyfund.springcloud.entity.User;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * User Controller
 *
 * @author Liuguanglei 18601767221@163.com
 * @create 2017-06-上午12:55
 */
@RestController
public class UserController {



    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private RestTemplate restTemplate;




    @Autowired
    private LoadBalancerClient loadBalancerClient;


    @GetMapping("/login")
    public User login(@RequestParam String name, @RequestParam String password){

        LOGGER.info("call user service login method");
        ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);

        return responseEntity.getBody();
    }



    @GetMapping("/list")
    public List<User> list(){
        User[] users=this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/list",User[].class);
        List<User> userList = Arrays.asList(users);
        return userList;

    }




    @GetMapping("user/get/{id}")
    public User get(@PathVariable Long id){

        return this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/get/id={1}",User.class,id);
    }


    /**
     * ribbon负载均衡测试方法
     */
    @GetMapping("/log-user-service-instance")
    public void logUserServiceInstance(){

        ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");

        LOGGER.info("serviceInstance info ---> serviceId is  "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
    }

}

从UserController中的login,get和list方法可以看出,我们将请求地址变更为http://SPRINGCLOUD-PROVIDER-USER-SERVICE,而SPRINGCLOUD-PROVIDER-USER-SERVICE是用户微服务的虚拟主机名,当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射城微服务的网络地址。

在新增的logUserServiceInstance方法中可以通过LoadBalancerClient的API更加直观的获取当前选择的用户微服务节点。

启动如下服务:
ribbon-loadbalance

访问地址:http://127.0.0.1:9994/log-user-service-instance

观察控制台输出:

2017-06-29 11:26:39.112 INFO 13474 — [nio-9994-exec-7] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 11:26:41.000 INFO 13474 — [nio-9994-exec-8] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 11:26:42.126 INFO 13474 — [nio-9994-exec-1] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 11:26:48.926 INFO 13474 — [nio-9994-exec-4] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995

可以看到此时请求会均匀分布到两个不同用户微服务节点上,说明已经实现了负载均衡。

application.properties

spring.application.name=springcloud-consumer-h5-ribbon-hystrix
server.port=9994

eureka.client.service-url.defaultZone=http://tony:666666@127.0.0.1:9998/eureka/,http://tony:666666@127.0.0.1:9997/eureka/
#eureka.instance.prefer-ip-address=true

##修改服务负载均衡规则为随机
springcloud-provier-user-service.ribbon.NFLoadBalanceRuleClassName=com.netflix.loadbalancer.RandomRule

十一 使用Feign实现声明式REST调用

Feign是Netflix公司开发的声明式、模板化的HTTP客户端,其主要用途是更加便捷、优雅的调用HTTP API,Spring Cloud在原有基础上使Feign支持SpringMVC注解,并且整合了Ribbon和Eureka。

所属maven模块 springcloud-consumer-h5-feign

之前的maven模块springcloud-consumer-h5-ribbon-hystrix是使用RestTemplate(负载均衡是使用ribbon实现)调用RESTful API。 如下的登录方法:

 @GetMapping("/login")
    public User login(@RequestParam String name, @RequestParam String password){

        LOGGER.info("call user service login method");
        ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);

        return responseEntity.getBody();
    }

由代码可知通过拼接字符串的方式构造URL,而在其他业务场景中可能还会有更多的参数,如果还以这种方式构造URL,那么就会变得更低效,难以维护。

引入Netflix公司开发的Feign实现声明式的RESTful API 调用

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.ekeyfund.springcloud</groupId>
    <artifactId>springcloud-consumer-h5-feign</artifactId>
    <packaging>jar</packaging>

    <name>springcloud-consumer-h5-feign</name>
    <description>SpringCloud Feign H5 Application</description>

    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>

    </parent>



    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudFeignH5Application</start-class>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

创建一个Feign接口,并添加@FeignClient注解

package com.ekeyfund.springcloud.feign;

import com.ekeyfund.springcloud.entity.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * User Feign Client
 *
 * @author tony 18601767221@163.com
 * @create 2017-06-29-下午2:44
 * @see
 * @since JDK1.8u133
 */
@FeignClient(value = "springcloud-provider-user-service") //
public interface UserFeignClient {

    @RequestMapping(value = "/list",method = RequestMethod.GET)
    List<User> list();

    @RequestMapping(value = "/login",method = RequestMethod.GET)
    User login(@RequestParam("name")  String name, @RequestParam("password") String password);

}

@FeignClient注解中的springcloud-provider-user-service是一个任意的客户端名称,用于创建Ribbon负载均衡器。

修改Controller,让其调用Feign接口

package com.ekeyfund.springcloud.controller;

import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.feign.UserFeignClient;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * User Feign Controller
 *
 * @author Liuguanglei 18601767221@163.com
 * @create 2017-06-下午1:50
 */
@RestController
public class UserFeignController {
    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserFeignController.class);


    @Autowired
    UserFeignClient userFeignClient;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping(value = "/list")
    public List<User> list(){

        return userFeignClient.list();
    }

    @GetMapping("/login")
    public User login(@RequestParam String name,@RequestParam String password){

        return userFeignClient.login(name,password);
    }

    /**
     * ribbon负载均衡测试方法
     * springcloud 将feign和ribbon以及eureka进行了集成
     */
    @GetMapping("/log-user-service-instance")
    public void loguserserviceinstance(){

        ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");

        LOGGER.info("serviceInstance info ---> serviceId is  "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
    }

}

启动类

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableDiscoveryClient
@EnableFeignClients //开启SpringCloud Feign的支持功能
@SpringBootApplication
public class SpringcloudFeignH5Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudFeignH5Application.class, args);
    }
}

启动如下服务:
feign

访问地址:http://127.0.0.1:9993/login?name=tony&password=666666

调用login接口返回的结果
调用login接口返回的结果

访问http://127.0.0.1:9993/log-user-service-instance

查看控制台日志输出
2017-06-29 14:56:56.341 INFO 16173 — [nio-9993-exec-4] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 14:56:58.055 INFO 16173 — [nio-9993-exec-5] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 14:56:59.310 INFO 16173 — [nio-9993-exec-6] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 14:57:05.287 INFO 16173 — [nio-9993-exec-7] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 14:57:06.340 INFO 16173 — [nio-9993-exec-8] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995

以上结果说明不仅实现了声明式的Restful API调用,还实现了客户端的负载均衡

十二 使用Hystrix实现微服务的容错处理

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.ekeyfund.springcloud</groupId>
    <artifactId>springcloud-consumer-h5-ribbon-hystrix</artifactId>
    <packaging>jar</packaging>

    <name>springcloud-consumer-h5-ribbon-hystrix</name>
    <description>Spring Cloud H5 Appliaction</description>


    <parent>
        <groupId>com.ekeyfund.springcloud</groupId>
        <artifactId>springcloud-parent</artifactId>
        <version>2.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>

    </parent>

    <properties>
        <start-class>com.ekeyfund.springcloud.SpringcloudH5RibbonHystrixApplication</start-class>

    </properties>

    <dependencies>



        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.8</version>
        </dependency>

    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

启动类:

添加@EnableCircuitBreaker为项目启动断路器支持

package com.ekeyfund.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableCircuitBreaker //启动断路器支持
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudH5RibbonHystrixApplication {




    public static void main(String[] args) {
        SpringApplication.run(SpringcloudH5RibbonHystrixApplication.class, args);
    }
}

修改UserController,让其中的list方法具备容错能力

package com.ekeyfund.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.ekeyfund.springcloud.entity.User;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * User Controller
 *
 * @author Liuguanglei 18601767221@163.com
 * @create 2017-06-上午12:55
 */
@RestController
public class UserController {



    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private RestTemplate restTemplate;




    @Autowired
    private LoadBalancerClient loadBalancerClient;


    @GetMapping("/login")
    public User login(@RequestParam String name, @RequestParam String password){

        LOGGER.info("call user service login method");
        ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);

        return responseEntity.getBody();
    }


    @HystrixCommand(fallbackMethod = "listFallback",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "50000"),
            @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "10000")
    },
            threadPoolProperties = {
                @HystrixProperty(name = "coreSize",value = "1"),
                    @HystrixProperty(name="maxQueueSize",value = "20")
            }
    )
    @GetMapping("/list")
    public List<User> list(){
        User[] users=this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/list",User[].class);
        List<User> userList = Arrays.asList(users);
        return userList;

    }


    /**
     * 当list方法所在的服务不可用时,会调用此方法
     * @return
     */
    public List<User> listFallback(){
        User user =new User();
        user.setName("admin");
        List<User> userList=new ArrayList<>();
        userList.add(user);
        return userList;
    }


    @GetMapping("user/get/{id}")
    public User get(@PathVariable Long id){

        return this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/get/id={1}",User.class,id);
    }


    /**
     * ribbon负载均衡测试方法
     */
    @GetMapping("/log-user-service-instance")
    public void loguserserviceinstance(){

        ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");

        LOGGER.info("serviceInstance info ---> serviceId is  "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
    }

}

由代码可知,为list方法添加一个回退方法listFallback,该方法与list方法具有相同的参数和返回值类型。

在list方法上,使用注解@HystrixCommand的fallBackMethod属性,指定回退方法是listFallback。

启动如下服务:
这里写图片描述

访问地址:http://127.0.0.1:9994/list
当服务状态可用时的返回结果
服务状态可用

关掉两个springcloud-provider-user-service进程后
user不可用
再次访问http://127.0.0.1:9994/list
返回默认用户信息
返回默认用户

maven中relativepath标签的含义 relativePath 的作用是为了找到父级工程的pom.xml;因为子工程需要继承父工程的pom.中的内容。然后relativePath 标签内的1.默认我们不用写,那就是../pom.xml,会从上级目录中获取parent的pom,我们建多模块的项目就是这个情况。2.设定一个空值将始终从仓库中获取,不从本地路径获取。很常见的场景就是使用springboot的时候3.这种也很显然,指定一个xxxx本地的路径,从xxxx本地路径获取parent的 pom。 阅读详情

相关推荐

springcloud微服务全家桶实战

一、整体介绍 二、概念学习 2.1 SpringCloud 2.2 Eureka 2.3 Ribbon 2.4 Feign 2.5 Hystrix 2.6 HystrixDashbord 2.7 Config 2.8 Zuul 2.9 Sleuth ZipKin 2.10 RabbitMQ 三、实战 3.1 springboot学习补充 3.11 Springboo...

qq_36481502的博客 4253

项目部署中Nacos报错提示 - Server check fail,please check server 127.0.0.1,port 9848 is available,error={}

最近项目中集成Nacos客户端,上线时需要部署到客户服务器上时,出现了这个报错,原因是我们项目中使用的Nacos客户端是2.x之上的版本,Nacos在这个版本中新增了gRPC通信,默认通信端口是Nacos中配置的server-addr的端口号加1000的偏移量,所以我们的报错是检查9848端口是否开放。第一个就是Nacos服务器上放开相应的端口,但部分项目环境可能不允许这么做,我碰到的这种,其次是选择把项目中Nacos的客户端版本降低到2.x之下,我们更换了1.4.4的版本。

qq_45150734的博客 1万+

Maven学习总结(55)——pom.xml 中 relativePath 属性的作用

搭建maven项目,子模块指定父模块试,经常会在parent下面出现relativePath类似下面: <parent> <groupId>net.itxw</groupId> <artifactId>test</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath/> </parent> 这个<parent>

科技D人生 5242

nacos配置出错,Server check fail, please check server 127.0.0.1 ,port 9848 is available , error ={}

配置文件在同时存在application.properties和application.yml时,应该修改application.yml为bootstrap.yml。

SOMECT的专栏 1万+

SpringCloud Alibaba微服务实战三十六 - 这大概算是使用Feign的正确姿势。

此文章为收费专栏文章,待流量变少时将移入收费专栏,且看且珍惜! 在SpringCloud架构体系中,微服务间的通信是基于Feign调用。而在实际使用Feign的过程中我们大概率会面临下面几个问题: Feign客户端放在消费端还是独立一个api层? Feign调用的接口如何要不要进行包装? Feign如何抓取业务生产端的业务异常? 这篇文章我们就来一起探讨一下这几个问题,希望看完能对你有所帮助。 首先我们先看看Feign的调用方式如何抉择? Feign的调用方式如何选择? 总体来说,Feign的调用方.

飘渺Jam的博客 2187

SpringCloud微服务实战

作者:禅与计算机程序设计艺术 1.简介 在微服务架构下,开发人员通常将应用拆分成一个个独立的服务,每个服务运行在自己的进程中,互相独立,互不干扰。因此,每个服务都需要独立的部署、测试、发布等一系列流程。而 Spring Cloud 是 Spring Framwork 的子项目,它为构建分布式系统提供了很

AI天才研究院 943

开始使用Spring Cloud实战微服务

开始使用Spring Cloud实战微服务 Spring Cloud 实战前提 Spring Cloud不一定适合所有人。学习之前需要了解需要具备什么样的技术能力,以及实战中会使用到哪些工具。 技术储备 Spring Cloud并不是面向零基础开发人员的,它有一定的学习曲线。 语言基础:Spring Cloud是一个基于Java语言的工具套件,所以学习他需要一定的Java基础。当然,Spring Cloud同样也支持使用Scala、Groovy等语言进行开发。 Spring Boot:Spring Clo

shi_zi_183的博客 370

SpringCloud实战:使用Sentinel构建可靠的微服务熔断机制

本文介绍了SpringCloud Gateway与Sentinel整合的实战指南。通过类比电路保险丝解释熔断机制原理,对比Sentinel与Hystrix的特性差异。详细演示了环境搭建步骤,包括控制台部署、服务端改造和网关集成方案,提供了关键配置类代码示例。文章还阐述了生产级流量控制策略(QPS限流、并发控制等)和熔断降级策略的配置方法,强调监控运维的重要性和最佳实践建议,如分级防护、渐进式规则验证等,帮助构建高可用的微服务系统。

技术Z先生的博客 1363

使用SpringCloud和Docker实战微服务学习资料:全面掌握微服务架构

使用SpringCloud和Docker实战微服务学习资料:全面掌握微服务架构 去发现同类优质开源项目:https://gitcode.com/ 使用SpringCloud和Docker实战微服务学习资料:项目的核心功能/场景 基于SpringCloud和Docker构建微服务架构,实现服务注册、配置管理、负载均衡等。 项目介绍 在当今云计算和分布式架构日益流行的背景下,微服务架构作为一种轻量级、...

gitblog_06770的博客 348

TikTok达人私信自动化:用Node.js和纯JavaScript实现WSS协议通信(附完整代码结构)

本文详细介绍了如何使用Node.js和WSS协议实现TikTok达人私信自动化系统。通过分层架构设计、WSS连接管理、消息加密策略及频率控制,开发者可以构建稳定高效的私信工具,突破平台API限制,适用于跨境电商和社交媒体运营场景。

weixin_30352645的博客 520

如何使用Calibre PERC抽出 点到点/p2p 电阻

@[TOC](如何使用Calibre PERC抽点到点(p2p)电阻) 一、前言 需要实现的功能是从gds中抽出net的点到点的电阻。如下图这样。 二、准备工作 lvs rule pex rule(必要时可以自己定义RC rule) gds p2p点的坐标 定义prode.defs 因为p2p的点是变化的,所以每次prode定义都要重新出,对应的perc rule也是根据每个p2p点的定义来的,perc rule也是每次都要重新出。如果需要可以写一个脚本自动出这些定义文件。 PROBE_POINTS

kobayashiyou的博客 1万+

使用springCloud构建微服务实战

Eureka是开源的服务发现组件,本身是一个基于REST的服务。包含两个组件:Eureka Server和Eureka Client。 (1)Eureka Server提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息,它会存储这些信息。 (2)Eureka Client是一个java客户端,用于简化与Eureka Server的交互。 一、创建服务发现组件(...

jiankang66的博客 755

4×V100部署Qwen3.8-Flash-Next:vLLM/GGUF实战

大模型本地部署的核心,是把参数量、量化精度、显存容量和推理框架放在同一张账本里算清楚。MoE 模型总参数量大但激活参数少,解码阶段更受显存带宽与 KV Cache 限制;通过 INT4/AWQ 或 GGUF 等 4-bit 量化可显著压缩权重占用,再结合 vLLM 张量并行、llama.cpp 分层切分提升多卡利用率。其技术价值在于让 V100 这类老架构 GPU 也能承载百亿级参数模型的私有化推理,并服务于 RAG 知识库、工作流 API、批量生成等场景。这次围绕 4×V100 32G 部署 Qwen3.

weixin_29061425的博客 349

SpringCLoud实战微服务之——微服务简介以及入门使用

微服务概述微服务是什么?微服务解决了什么问题?微服务有什么特点?单体架构是什么?一个归档包包含了应用所有功能的应用程序,我们通常称之为单体应用。架构单体应用的架构风格,我们称之为单体架构,这是一种比较传统的架构风格。单体架构存在的缺点:1.复杂性逐渐变高。2.技术债务逐渐上升。3.部署速度逐渐变慢。4.阻碍技术创新。5.无法按需收缩。架构的演进:1.单体架构2.SOA3.微服务什么是微服务?Mar...

6518

Word | 简单可操作的快捷公式编号、右对齐和引用方法

在理工科论文的写作中,涉及到大量的公式输入,我们希望能够按照章节为公式进行编号,并且实现公式居中,编号右对齐的效果。网上有各种各样的方法来实现,操作繁琐和简单的混在一起,让没有接触过公式编号的人感觉比较混乱。笔者自己博览相关博客,亲身实践,总结出一套自认为比较简便的公式编号、对齐和引用方法,在这里分享给大家~

qq_43466146的博客 3万+

染色体外环状DNA:肿瘤研究新方向

染色体外环状DNA(ecDNA)是癌症中活化原癌基因扩增的主要方式。区别于常染色体,这类生物大分子主要以环状的形式存在于细胞核当中,长度分布在103-106数量级,无中心粒。ecDNA序列中包含一个或者多个基因与调控元件,结构开放,转录活性强,相比于在棒状染色体结构基因,ecDNA的生物功能活性更强。癌症中,成环的基因区域拷贝数往往增高,而超过一半的高拷贝数事件是ecDNA驱动生成。...

blackjimmy的博客 766

基于SpringCloud的微服务架构实战指南 - 使用microservice_arch_springcloud

基于SpringCloud的微服务架构实战指南 - 使用microservice_arch_springcloud 项目介绍 microservice_arch_springcloud 是一个基于Spring Cloud实现的微服务架构示例工程,旨在为《凤凰架构》一书提供配套的实践案例。该项目展示如何利用Spring Cloud搭建微服务环境,涵盖配置中心、服务发现、路由网关、服务保护等多个关键组...

gitblog_00275的博客 548

Python爬虫实战:用Playwright+Transformer做财经新闻事件驱动分析,我靠它抓住了算力政策的涨停机会

对我来说,这套系统最大的价值不是抓住了几次涨停,而是把“看新闻→分析→决策”的全流程自动化、量化了——不用再被股吧的情绪带偏,不用再熬夜刷新闻,而是用数据和模型做理性判断。更重要的是,搭建这套系统的过程,我吃透了Playwright动态爬虫、Transformer文本分析、量化数据对接这些核心技能,这些技能不管是做量化、做数据分析师,还是做其他领域的爬虫,都能用得上。现在打开PyCharm,跟着代码跑一遍,你也能从海量财经新闻里,挖出属于自己的事件驱动信号——与其靠感觉炒股,不如靠数据说话。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1393

SpringCloud微服务全栈路线企业实战完整使用

SpringCloud微服务全部零基础即可学会全栈开发的路线,涵盖前端到后端技术栈,包含微服务架构、项目实战及部署流程。内容包括:1.开发工具箱与运维基础;2.NB项目架构模块详解(授权服务、用户权限管理等);3.项目搭建教程;4.微服务组件配置;5.K8S部署;6.三大实战项目(八卦系统、证件照系统、恋爱小程序)。该路线特别适合零基础学习者,通过接口对接、云部署等实战内容快速掌握全栈技能,并提供持续技术支持和项目扩展空间。

小坏说Java,一个专注Java微服务架构的实战,Java全栈教程,SpringBoot, SpringCloud微服务到分布式、高并发架构,大数据开发,运维实战,K8S、Docker容器为一体的教程,结合企业级内部的技术栈学习 1509

验证码破解2.0:Python+CV大模型搞定滑块/点选验证

核心创新:基于YOLOv8+SAM CV大模型的端到端验证码破解方案,无需人工设计特征,适配多种验证码类型;核心价值高成功率:滑块验证≥95%,点选验证≥90%,远超传统CV方案;低延迟:单验证码破解≤2秒,满足高并发采集需求;行为模拟:生成符合人类操作的轨迹,绕过行为检测;易扩展:统一架构支持新增验证码类型,仅需少量标注数据微调;技术要点采用YOLOv8实现快速目标检测,SAM实现像素级分割;生成物理规律的人类轨迹,模拟真实操作行为;

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1261

YOLOv8魔改系列:将CIoU替换成Wise-IoU,飞速涨点

将YOLOv8中CIoU替换成Wise-IoU,实现飞速涨点

qq_66386046的博客 2679

springcloud 微服务管理之 admin 实战使用(九)

Springcloud admin 是基于spring-boot-starter-actuator 实现的,admin 是根据统计出来的数据来进行展示的,可以很好的监控整 个微服务系统中的实例运行情况信息,前提结合注册中心才能拿到数据。 1、actuator 负责统计数据,引入jar: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-s...

nandao158的博客 1014

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【JAVA应用实战-SpringCloud】(三)SpringCloud微服务模块使用docker进行部署

本文介绍了将SpringCloud集成Eureka注册中心的三个代码模块打包成JAR文件的过程,并详细说明了部署脚本的文件和脚本结构。最后,文章还提到了测试运行的相关内容,确保部署后的应用能够正常运行。通过这一系列步骤,开发者可以有效地将SpringCloud应用与Eureka注册中心集成,并进行部署和测试。

420

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【JAVA应用实战-SpringCloud】(四)SpringCloud微服务模块使用docker-compose进行部署

本文介绍了SpringCloud集成Eureka注册中心的部署与测试流程。首先,准备工作包括将代码模块打包为三个jar文件。接着,部署脚本部分详细说明了文件和脚本结构,并提供了docker部署脚本docker-compose.yml,用于配置Eureka服务、User服务和Order服务的实例及其环境变量。此外,还包含一个deploy_docker_compose.sh脚本,用于构建镜像、启动、停止和删除服务。最后,测试运行部分指导如何构建镜像、启动容器,并通过访问特定URL来测试Eureka服务、User

806

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

上一篇: SpringCloud Eureka实战
下一篇: Ubuntu16.04安装QT5.9
boy_cto_tony
博客等级 码龄11年 116粉丝 50原创
评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值