本篇主要基于consul注册中心来实现的
consul官网下载:https://www.consul.io/downloads.html
cmd 命令窗口执行:consul agent -dev
consul 自带 UI 界面,打开网址:http://localhost:8500 ,可以看到当前注册的服务界面
一、feign接口提供者
1、pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<groupId>com.zhanghan</groupId>
<artifactId>zh-monitor</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>zh-monitor</name>
<description>zhanghan monitor for Spring Boot</description>
<!--全局管理springcloud版本,并不会引入具体依赖-->
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入consul依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<!-- spring boot admin begin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-client</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>cn.shuibo</groupId>
<artifactId>rsa-encrypt-body-spring-boot</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>zh-monitor</finalName>
</build>
</project>
2、application.properties
server.port=8081
spring.application.name=zhouzy
#spring.boot.admin.client.url=http://localhost:8081
#****************************Security***************************
#spring.boot.admin.client.username=admin
#spring.boot.admin.client.password=admin
#management.endpoints.web.base-path = /
#Open all page nodes by default only two nodes of health and info are enabled.
#management.endpoints.web.exposure.include = *
#Display health specific information No details are displayed by default
#management.endpoint.health.show-details = always
# Redis\u6570\u636E\u5E93\u7D22\u5F15\uFF08\u9ED8\u8BA4\u4E3A0\uFF09
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
spring.cloud.gateway.routes.filters.name=RequestRateLimiter
spring.cloud.gateway.routes.filters.args.key-resolver='#{@hostAddrKeyResolver}'
spring.cloud.gateway.routes.filters.args.redis-rate-limiter.replenishRate=1
spring.cloud.gateway.routes.filters.args.redis-rate-limiter.burstCapacity=3
spring.cloud.consul.discovery.enabled=true
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.serviceName=zhouzy
3、http接口
package com.zhouzy.boot.zhouzyBoot.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.zhouzy.boot.zhouzyBoot.model.User;
@RestController
public class ApiController {
private static Logger logger = LoggerFactory.getLogger(ApiController.class);
@Autowired
RedisTemplate redisTemplate;
@SuppressWarnings("unchecked")
@RequestMapping(value = "/api/miaosha",method =RequestMethod.POST)
public String miaosha(@RequestBody User user,HttpServletRequest request) throws Exception{
//每个用户只能抢一次,
String userId = user.getUserId();
int shopNum = Integer.valueOf(redisTemplate.opsForValue().get("shopNum").toString());
logger.info("当前商品数量:{}",shopNum);
if(Integer.valueOf(redisTemplate.opsForValue().get("shopNum").toString()) <= 0){
logger.info("商品数量不够了,还有:{}",redisTemplate.opsForValue().get("shopNum"));
return "fail";
}
Integer num = redisTemplate.opsForValue().get(userId) == null ? 0 : Integer.valueOf(redisTemplate.opsForValue().get(userId).toString());
if(num > 0){
logger.info("用户:{},已经抢过了,当前数量:{}",userId,num);
return null;
}
// 开启事务支持,在同一个 Connection 中执行命令
redisTemplate.execute((RedisOperations operations) -> {
//特别注意,在事务方法块里都用operations操作数据,而不是用redisTemplate
//这个是没有考虑并发的问题场景,可以试试看结果如何
List<Object> result = null;
do{
operations.watch("shopNum");
int shopNum2 = Integer.valueOf(String.valueOf(operations.opsForValue().get("shopNum")));
int num2 = operations.opsForValue().get(userId) == null ? 0 : Integer.valueOf(operations.opsForValue().get(userId).toString());
if(num2 > 0){
logger.info("用户:{},已经抢过了,当前数量:{}",userId,num2);
break;
}
//shopNum = shopNum - 1;
operations.multi();//开启事务
operations.opsForValue().set(userId, num2+1);
operations.opsForValue().set("shopNum", shopNum2-1);
result = operations.exec();//执行
logger.info(JSONObject.toJSONString(result));
try {
Thread.sleep(RandomUtils.nextInt(1, 100));
} catch (Exception e) {
e.printStackTrace();
}
}while (CollectionUtils.isEmpty(result)); //如果失败则重试,注意不是null,而是判断非空,包括长度为0
return result;
});
shopNum = Integer.valueOf(redisTemplate.opsForValue().get("shopNum").toString());
logger.info("执行后当前商品数量:{}",shopNum);
return "success";
}
}
4、启动
package com.zhouzy.boot.zhouzyBoot;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhouzy.boot.zhouzyBoot.config.AxinDispatcherServlet;
@EnableDiscoveryClient
@EnableAutoConfiguration
@SpringBootApplication
class WebApplication{
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@Bean(name="remoteRestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
@Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new AxinDispatcherServlet();
}
@Bean(name = "template")
public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {
// 创建RedisTemplate<String, Object>对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 定义Jackson2JsonRedisSerializer序列化对象
Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
StringRedisSerializer stringSerial = new StringRedisSerializer();
// redis key 序列化方式使用stringSerial
template.setKeySerializer(stringSerial);
// redis value 序列化方式使用jackson
template.setValueSerializer(jacksonSeial);
// redis hash key 序列化方式使用stringSerial
template.setHashKeySerializer(stringSerial);
// redis hash value 序列化方式使用jackson
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
template.setEnableTransactionSupport(true);
return template;
}
}
二、Feign接口消费者
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<groupId>com.zhanghan</groupId>
<artifactId>zh-monitor</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>zh-monitor</name>
<description>zhanghan monitor for Spring Boot</description>
<!--全局管理springcloud版本,并不会引入具体依赖-->
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入consul依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- spring boot admin begin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-client</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>cn.shuibo</groupId>
<artifactId>rsa-encrypt-body-spring-boot</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>zh-monitor</finalName>
</build>
</project>
2、application.properties
server.port=8083
spring.application.name=zhouzy2
#spring.boot.admin.client.url=http://localhost:8081
#****************************Security***************************
#spring.boot.admin.client.username=admin
#spring.boot.admin.client.password=admin
#management.endpoints.web.base-path = /
#Open all page nodes by default only two nodes of health and info are enabled.
#management.endpoints.web.exposure.include = *
#Display health specific information No details are displayed by default
#management.endpoint.health.show-details = always
# Redis\u6570\u636E\u5E93\u7D22\u5F15\uFF08\u9ED8\u8BA4\u4E3A0\uFF09
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
spring.cloud.consul.discovery.enabled=true
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.serviceName=zhouzy2
logging.level.com.zhouzy.boot.zhouzyBoot.service:debug
3、配置日志
package com.zhouzy.boot.zhouzyBoot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Logger;
@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Logger.Level的值有以下选择:
NONE,无记录(DEFAULT)。
BASIC,只记录请求方法和URL以及响应状态代码和执行时间。
HEADERS,记录基本信息以及请求和响应标头。
FULL,记录请求和响应的头文件,正文和元数据。
4、配置feign接口
package com.zhouzy.boot.zhouzyBoot.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import com.zhouzy.boot.zhouzyBoot.model.User;
@FeignClient(value="zhouzy",fallback = FeignApiFallBack.class)
public interface ApiService {
@PostMapping("/api/miaosha")
public String miaosha(User user);
}
5、接口调用
package com.zhouzy.boot.zhouzyBoot.controller;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.zhouzy.boot.zhouzyBoot.model.User;
import com.zhouzy.boot.zhouzyBoot.service.ApiService;
@RestController
public class ApiController {
private static Logger logger = LoggerFactory.getLogger(ApiController.class);
@Autowired
ApiService apiService;
@RequestMapping(value = "/api/test",method =RequestMethod.GET)
public String test(HttpServletRequest request) throws Exception{
User user = new User();
user.setName("name01");
user.setUserId("1001");
return apiService.miaosha(user);
}
}
6、启动
package com.zhouzy.boot.zhouzyBoot;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhouzy.boot.zhouzyBoot.config.AxinDispatcherServlet;
import feign.Logger;
@EnableDiscoveryClient
@EnableAutoConfiguration
@SpringBootApplication
@EnableFeignClients(basePackages = { "com.zhouzy.boot.zhouzyBoot.service" })
class WebApplication{
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@Bean(name="remoteRestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
@Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new AxinDispatcherServlet();
}
@Bean(name = "template")
public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {
// 创建RedisTemplate<String, Object>对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 定义Jackson2JsonRedisSerializer序列化对象
Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
StringRedisSerializer stringSerial = new StringRedisSerializer();
// redis key 序列化方式使用stringSerial
template.setKeySerializer(stringSerial);
// redis value 序列化方式使用jackson
template.setValueSerializer(jacksonSeial);
// redis hash key 序列化方式使用stringSerial
template.setHashKeySerializer(stringSerial);
// redis hash value 序列化方式使用jackson
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
template.setEnableTransactionSupport(true);
return template;
}
}
7、结果
一般比较复杂的接口最好打印下日志,便于出问题是分析!