新建数据库 db2020
cloud-provider-payment8001
<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">
<parent>
<artifactId>spring-cloud-demoartifactId>
<groupId>com.cloud.demogroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-provider-payment8001artifactId>
<dependencies>
<dependency>
<groupId>com.cloud.demogroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
此处的eureka的maven信息为
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
server:
port: 8001
spring:
application:
name: payment-provider-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver #mysql驱动包
url: jdbc:mysql://localhost:3306/db2020?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding-utr-8&useSSL=false
username: root
password: south755
eureka:
user:
name: admin
password: 123456
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# eureka 注册地址
defaultZone: http://${eureka.user.name}:${eureka.user.password}@localhost:7001/eureka #单机版
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.demo.common.entity #所有Entity别名类所在包
package com.demo.payment;
@SpringBootApplication
//前往eureka 注册中心注册服务
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
System.out.println("PaymentMain8001 已启动");
}
}
常规crud操作,推荐个sql生成业务代码的网站,总有会用到的时候
在线Sql代码生成
CREATE TABLE `payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`serial` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
@Data
public class Payment implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* serial
*/
private String serial;
public Payment() {
}
}
@Mapper
public interface PaymentDao {
/**
* [新增]
* @author PeiKangLe
* @date 2020/05/14
**/
int insert(Payment payment);
/**
* [刪除]
* @author PeiKangLe
* @date 2020/05/14
**/
int delete(Long id);
/**
* [更新]
* @author PeiKangLe
* @date 2020/05/14
**/
int update(Payment payment);
/**
* [查询] 根据主键 id 查询
* @author PeiKangLe
* @date 2020/05/14
**/
Payment load(Long id);
}
<mapper namespace="com.demo.payment.dao.PaymentDao">
<resultMap id="BaseResultMap" type="com.demo.common.entity.Payment" >
<result column="id" property="id" />
<result column="serial" property="serial" />
resultMap>
<sql id="Base_Column_List">
id,
serial
sql>
<insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="Payment">
INSERT INTO payment
<trim prefix="(" suffix=")" suffixOverrides=",">
serial
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
#{serial}
if>
trim>
insert>
<delete id="delete" >
DELETE FROM payment
WHERE id = #{id}
delete>
<update id="update" parameterType="Payment">
UPDATE payment
<set>
serial = #{serial}if>
set>
WHERE id = #{id}
update>
<select id="load" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List" />
FROM payment
WHERE id = #{id}
select>
mapper>
public interface PaymentService {
/**
* 新增
*/
public int insert(Payment payment);
/**
* 删除
*/
public int delete(Long id);
/**
* 更新
*/
public int update(Payment payment);
/**
* 根据主键 id 查询
*/
public Payment load(Long id);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
@Override
public int insert(Payment payment) {
return paymentDao.insert(payment);
}
@Override
public int delete(Long id) {
return paymentDao.delete(id);
}
@Override
public int update(Payment payment) {
return paymentDao.update(payment);
}
@Override
public Payment load(Long id) {
return paymentDao.load(id);
}
}
为返回结果方便,我们先在Common模块里添加统一返回对象
public class ResultModel extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
public static final String CODE_TAG = "code";
/**
* 返回内容
*/
public static final String MSG_TAG = "msg";
/**
* 数据对象
*/
public static final String DATA_TAG = "data";
/**
* 状态类型
*/
public enum Type {
/**
* 成功
*/
SUCCESS(0),
/**
* 警告
*/
WARN(301),
/**
* 错误
*/
ERROR(500);
private final int value;
Type(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
public ResultModel() {
}
/**
* 初始化一个新创建的 ResultModel 对象
*
* @param type 状态类型
* @param msg 返回内容
*/
public ResultModel(Type type, String msg) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param type 状态类型
* @param msg 返回内容
* @param data 数据对象
*/
public ResultModel(Type type, String msg, Object data) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
if (!Objects.isNull(data)) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static ResultModel success() {
return ResultModel.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static ResultModel success(Object data) {
return ResultModel.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static ResultModel success(String msg) {
return ResultModel.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static ResultModel success(String msg, Object data) {
return new ResultModel(Type.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static ResultModel warn(String msg) {
return ResultModel.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static ResultModel warn(String msg, Object data) {
return new ResultModel(Type.WARN, msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static ResultModel error() {
return ResultModel.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static ResultModel error(String msg) {
return ResultModel.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static ResultModel error(String msg, Object data) {
return new ResultModel(Type.ERROR, msg, data);
}
}
再来添加Controller
@Slf4j
@RestController
@RequestMapping(value = "/Payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
/**
* [新增]
*
* @author PeiKangLe
* @date 2020/05/14
**/
@PostMapping("/insert")
public ResultModel insert(@RequestBody Payment payment) {
int insert = paymentService.insert(payment);
log.info("添加信息:{},添加结果:{}", payment.toString(), insert);
return insert > 0 ? ResultModel.success("添加成功") : ResultModel.error("添加失败");
}
/**
* [刪除]
*
* @author PeiKangLe
* @date 2020/05/14
**/
@DeleteMapping("/{id}")
public ResultModel delete(@PathVariable("id") Long id) {
int delete = paymentService.delete(id);
log.info("删除信息:{},删除结果:{}", id, delete);
return delete > 0 ? ResultModel.success("删除成功") : ResultModel.error("删除失败");
}
/**
* [更新]
*
* @author PeiKangLe
* @date 2020/05/14
**/
@PutMapping("/update")
public ResultModel update(Payment payment) {
int update = paymentService.update(payment);
log.info("修改信息:{},修改结果:{}", payment.toString(), update);
return update > 0 ? ResultModel.success("修改成功") : ResultModel.error("修改失败");
}
/**
* [查询] 根据主键 id 查询
*
* @author PeiKangLe
* @date 2020/05/14
**/
@GetMapping("/{id}")
public ResultModel load(@PathVariable("id") Long id) {
return ResultModel.success("查询成功", paymentService.load(id));
}
}
cloud-consumer-order80
<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">
<parent>
<artifactId>spring-cloud-demoartifactId>
<groupId>com.cloud.demogroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-order80artifactId>
<dependencies>
<dependency>
<groupId>com.cloud.demogroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 80
spring:
application:
name: order-consumer-service
eureka:
user:
name: admin
password: 123456
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# eureka 注册地址
defaultZone: http://${eureka.user.name}:${eureka.user.password}@localhost:7001/eureka #单机版
同样用@EnableEurekaClient将我们的服务注册到EurekaServer
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
System.out.println("Oder80 启动成功");
}
}
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
说明:Payment通用的,为了方便,我就放到common模块里去了
@Slf4j
@RestController
@RequestMapping("/Order")
public class OrderController {
@Resource
private RestTemplate restTemplate;
private static final String paymentUrl = "http://localhost:8001/Payment/";
@GetMapping("/{id}")
public ResultModel getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(paymentUrl+id, ResultModel.class );
}
@PostMapping("/create")
public ResultModel createPayment(Payment payment){
return restTemplate.postForObject(paymentUrl+"insert",payment, ResultModel.class );
}
}
可以看到,我们两个服务都已经成功注册到EurekaServer里了
之前遇见的上面的提示,就是告诉我们Eureka进入了保护模式。
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,这就可能变得非常危险了----因为微服务本身是健康的,此时本不应该注销这个微服务。
Eureka Server通过“自我保护模式”来解决这个问题----当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
自我保护模式是一种对网络异常的安全保护措施。使用自我保护模式,而已让Eureka集群更加的健壮、稳定。
简单来说,就是有些时刻微服务不可用了
Eureka不会马上清理它,会对该微服务的信息进行保护。
eureka:
server:
# 是否开启自我保护模式,默认是true
enable-self-preservation: false
# 扫描失效服务的时间间隔。单位 毫秒。 默认值 60 * 1000
eviction-interval-timer-in-ms: 1000
在Spring Cloud中,可以通过HTTP请求的方式,通知Eureka Client优雅停服,这个请求一旦发送到Eureka Client,那么Eureka Client会发送一个shutdown请求到Eureka Server,Eureka Server接收到这个shutdown请求后,会在服务列表中标记这个服务的状态为down,同时Eureka Client应用自动关闭。这个过程就是优雅停服。
如果使用了优雅停服,则不需要再关闭Eureka Server的服务保护模式。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
# 开启应用关闭端口
management.endpoint.shutdown.enabled=true
# 暴露shutdown的web端口
必须通过POST请求向Eureka Client发起一个shutdown请求。请求路径为:http://ip:port/shutdown。可以通过任意技术实现,如:HTTPClient、form表单,AJAX等。
建议使用优雅停服方式来关闭Application Service/Application Client服务。
spring.application.name=xxxxxxxxxxxxx :应用名称配置,将会出现在 Eureka 注册中心 Application 列
server.port=8701 :应用端口,默认值为 8761
eureka.instance.hostname= server1 :服务注册中心应用实例主机名
eureka.instance.ip-address=127.0.0.1 :应用实例ip
eureka.instance.prefer-ip-address=false :客户端向注册中心注册时,相较于 hostname 是否有限使用 ip。在服务中心注册后,鼠标放到服务的 Status 列的链接上,无需点击,左下角能看出配置的变化。
eureka.instance.environment=dev :该实例的环境配置
eureka.client.register-with-eureka=false :是否将自己注册到 Eureka 注册中心。单机情况下的 Eureka Server 不需要注册,集群的 Eureka Server 以及 Eureka Client 需要注册。默认值 true
eureka.client.fetch-registry=false :是否需要从注册中心检索获取服务的注册信息。单机情况下的 Eureka Server 不需要获取。集群的 Eureka Server 以及 Eureka Client 需要获取。默认值 true
eureka.client.service-url.defaultZone= http://${spring.security.user.name}:${spring.security.user.password}@server1:8081/eureka/ :Eureka 服务的地址信息,中间的占位符为安全认证开启时使用,如果 Eureka Server 为集群状态,则逗号分隔,依次书写即可。
eureka.server.enable-self-preservation = false :是否开启自我保护模式,eureka server默认在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败。默认 true
eureka.server.eviction-interval-timer-in-ms=10000 :扫描失效服务的时间间隔。单位 毫秒。 默认值 60 * 1000
security.basic.enabled=true :开启 Eureka 安全认证
spring.security.user.name=root :安全认证用户名
spring.security.user.password=123456 :安全认证密码
eureka.client.registry-fetch-interval-seconds=30 :客户端获取服务注册信息时间间隔,单位 秒。默认 30
eureka.instance.appname=eureka-client :服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.lease-expiration-duration-in-seconds=90 :服务的失效时间,失效的服务将被注册中心删除。时间间隔为最后一次注册中心接收到的心跳时间。单位 秒,默认 90
eureka.instance.lease-renewal-interval-in-seconds=30 :应用实例给注册中心发送心跳的间隔时间,用于表明该服务实例可用。单位 秒。默认30
eureka.client.eureka-server-connect-timeout-seconds=5 :client 连接 Eureka 注册中心的超时时间,单位 秒,默认 5
eureka.client.eureka-server-read-timeout-seconds=8 :client 对 Eureka 服务器读取信息的超时时间,单位 秒,默认 8
eureka.client.eureka-connection-idle-timeout-seconds=30 :client 连接 Eureka 服务端后空闲等待时间,单位 秒,默认 30
eureka.client.eureka-server-total-connections=200 :client 到 所有Eureka 服务端的连接总数,默认 200
eureka.client.eureka-server-total-connections-per-host=50 :client 到 Eureka 单服务端的连接总数,默认 50
application.yml文件里
eureka节点里可以添加
instance:
#Eureka 面板 显示服务名
instance-id: 服务名
#Eureka 面板 ,鼠标指向服务 显示ip地址信息
prefer-ip-address: true
配置完成后,eureka视图里就直接显示自定义的服务名称了,鼠标悬浮会显示ip地址,这对于查看节点环境也是很方便的
参考文章:【SpringCloud】Eureka注册中心(二)