微服务项目,应用Eureka, Redis, RabbitMQ
1.设计思路
2.技术架构
(1)Eureka Client:
时间服务(leyouTimeServer,端口号8000):为页面服务提供时间统一的接口。
商品服务(leyouStock,端口号7000):对外提供的接口(商品列表、商品详情、秒杀政策)。
库存服务(leyouStorage,端口号6001):队列监听,在队列中提取消息与数据库交互减少库存。
会员服务(leyouUser,端口号5000):为页面服务提供会员数据接口,会员的添加、修改、登录。
订单服务(leyouOrder,端口号4000):队列监听,在队列中提取消息与数据库交互生成订单。
页面服务(leyouClient,端口号3000):为前端页面提供数据接口。
(2)Eureka Server:
注册中心(leyouServer,端口号9000)各服务都在注册中心进行注册。
配置中心 (leyouConfig):提供所有服务需要的配置。
(3)Redis的应用
(4)RabbitMQ的应用:
3.整体项目目录
4.创建Eureka注册中心(端口号9000),leyouServer
(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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>leyou</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2)配置文件application.properties
server.port=9000
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
eureka.instance.hostname=localhost
spring.application.name=leyou-server
#不从服务器拿服务信息
eureka.client.fetch-registry=false
#不在服务端注册
eureka.client.register-with-eureka=false
(3)启动类
① @EnableEurekaServer,意思为启动Eureka服务端
@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
5.创建时间服务(端口号8000),leyouTimeServer
(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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>leyou</artifactId>
<version>1.0-SNAPSHOT</version>
<name>leyouTimeServer</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2)配置文件application.properties
server.port=8000
spring.application.name=leyou-time-server
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
(3)启动类
① @EnableEurekaClient,意思为启动Eureka客户端
@SpringBootApplication
@EnableEurekaClient
public class TimeServerApplication {
public static void main(String[] args) {
SpringApplication.run(TimeServerApplication.class, args);
}
}
(3)TimeController.java
用途:给前端秒杀提供统一的时间标准,在TimeController里写入如下代码:
@RestController
public class TimeController {
@RequestMapping(value = "/getTime")
public String getTime(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
return simpleDateFormat.format(new Date());
}
}
5.创建商品服务(端口号7000),leyouStock
(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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>leyou</artifactId>
<version>1.0-SNAPSHOT</version>
<name>leyouStock</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2)配置文件application.properties
server.port=7000
spring.application.name=leyou-stock
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://172.20.175.147:3305/code1_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
#redis数据库编号,存在0~15共16个数据库
spring.redis.database=0
#redis服务器IP
spring.redis.host=127.0.0.1
#redis端口号
spring.redis.port=6379
#redis密码
spring.redis.password=leyou
#redis请求超时时间,超过此值redis自动断开连接
spring.redis.timeout=10000ms
#jedis最大连接数,超过此值则提示获取不到连接异常
spring.redis.jedis.pool.max-active=32
#jedis最大等待时间,超过此值会提示连接超时异常
spring.redis.jedis.pool.max-wait=10000ms
#jedis最大等待连接数
spring.redis.jedis.pool.max-idle=32
#jedis最小等待连接数
spring.redis.jedis.pool.min-idle=0
(3)启动类
① @EnableEurekaClient,意思为启动Eureka客户端
②加入负载均衡RestTemplate
@SpringBootApplication
@EnableEurekaClient
public class LeyouStockApplication {
public static void main(String[] args) {
SpringApplication.run(LeyouStockApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
(4)StockController.java
@RestController
public class StockController {
@Autowired
private IStockService iStockService;
@RequestMapping(value = "/getStockList")
public Map<String, Object> getStockList(){
return iStockService.getStockList();
}
@RequestMapping("/getStock/{sku_id}")
public Map<String,Object> getStock(@PathVariable("sku_id") String sku_id){
return iStockService.getStock(sku_id);
}
@RequestMapping(value = "/insertLimitPolicy/{json}")
public Map<String, Object> insertLimitPolicy(@PathVariable("json") String json){
Map<String, Object> policyInfo = JSONObject.parseObject(json, Map.class);
return iStockService.insertLimitPolicy(policyInfo);
}
}
(5)StockServiceImpl.java
①将秒杀政策写入Redis和MySQL
//工具类,将信息写入MySQL和redis
@Override
@Transactional(rollbackOn = Exception.class)
public Map<String,Object> insertLimitPolicy(Map<String,Object> policyInfo){
HashMap<String, Object> resultMap = new HashMap<>();
//1.传入参数判读
if(policyInfo==null || policyInfo.isEmpty()){
resultMap.put("result",false);
resultMap.put("msg","您传入的参数有误");
return resultMap;
}
//2. 写入redis,StringRedisTemplate
//redis 中取key: LIMIT_POLICY_{sku_id}, value: policyInfo --> String
//设置redis存在的有效期, 有效期:结束时间减去当前时间
long diff = 0;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class);
try {
Date end_time = simpleDateFormat.parse(policyInfo.get("end_time").toString());
Date now_time = simpleDateFormat.parse(now);
diff = (end_time.getTime()-now_time.getTime())/1000; //有效期为秒
if (diff<0){
resultMap.put("result",false);
resultMap.put("msg","结束时间不能小于当前时间");
return resultMap;
}
} catch (ParseException e) {
e.printStackTrace();
}
//3. 存入MySQL
boolean result = iStockDao.insertLimitPolicy(policyInfo);
//如果没有执行成功,返回错误信息
if (!result){
resultMap.put("result", false);
resultMap.put("msg", "数据库写入政策时失败!");
return resultMap;
}
String policy = JSON.toJSONString(policyInfo);
//4.1 商品政策写入Redis,并设置redis的有效期
stringRedisTemplate.opsForValue().set("LIMIT_POLICY_"+policyInfo.get("sku_id"),policy,diff, TimeUnit.SECONDS);
//4.2 从MySQL中将该商品信息取出写入redis
ArrayList<Map<String, Object>> list = iStockDao.getStock(policyInfo.get("sku_id").toString());
String sku = JSON.toJSONString(list.get(0));
stringRedisTemplate.opsForValue().set("SKU_"+policyInfo.get("sku_id").toString(),sku,diff, TimeUnit.SECONDS);
//5. 返回正常信息
resultMap.put("result", true);
resultMap.put("msg", "政策写入完毕!");
return resultMap;
}
②从Redis中得到商品对应的政策
把Map/JSON形的String转换为Map
//工具类,从redis中得到商品政策
private Map<String,Object> getLimitPolicy(ArrayList<Map<String,Object>> list){
HashMap<String, Object> resultMap = new HashMap<>();
for(Map<String,Object> skuMap: list){
//3.1 根据sku_id,从redis取出产品相应的政策
String policy = stringRedisTemplate.opsForValue().get("LIMIT_POLICY_" + skuMap.get("sku_id").toString());
System.out.println(policy);
//3.2 判断输入的政策是否为空
if(policy!=null && !policy.equals("")){
//把Map形的String转换为Map
Map policyInfo = JSONObject.parseObject(policy, Map.class);
//3.3 判断开始时间小于等于当前时间,并且当前时间小于等于结束时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//3.3.1用RestTemplate操作URL,获取值
String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class);
try {
Date end_time = simpleDateFormat.parse(policyInfo.get("end_time").toString());
Date begin_time = simpleDateFormat.parse(policyInfo.get("begin_time").toString());
Date now_time = simpleDateFormat.parse(now);
if(begin_time.getTime()<=now_time.getTime()&&now_time.getTime()<=end_time.getTime()){
skuMap.put("limitPrice",policyInfo.get("price"));
skuMap.put("limitQuanty", policyInfo.get("quanty"));
skuMap.put("limitBeginTime", policyInfo.get("begin_time"));
skuMap.put("limitEndTime", policyInfo.get("end_time"));
skuMap.put("nowTime", now);
}
} catch(ParseException e) {
e.printStackTrace();
}
}
}
resultMap.put("result",true);
resultMap.put("msg","");
return resultMap;
}
6.常用数据写入Redis,设置生命周期
stringRedisTemplate.opsForValue(). set("LIMIT_POLICY_" + policyInfo.get("sku_id"), policy, diff, TimeUnit.SECONDS);
//工具类,将信息写入MySQL和redis
@Override
@Transactional(rollbackOn = Exception.class)
public Map<String, Object> insertLimitPolicy(Map<String, Object> policyInfo) {
HashMap<String, Object> resultMap = new HashMap<>();
//1.传入参数判读
if (policyInfo == null || policyInfo.isEmpty()) {
resultMap.put("result", false);
resultMap.put("msg", "您传入的参数有误");
return resultMap;
}
//2. 写入redis,StringRedisTemplate
//redis 中取key: LIMIT_POLICY_{sku_id}, value: policyInfo --> String
//设置redis存在的有效期, 有效期:结束时间减去当前时间
long diff = 0;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class);
try {
Date end_time = simpleDateFormat.parse(policyInfo.get("end_time").toString());
Date now_time = simpleDateFormat.parse(now);
diff = (end_time.getTime() - now_time.getTime()) / 1000; //有效期为秒
if (diff < 0) {
resultMap.put("result", false);
resultMap.put("msg", "结束时间不能小于当前时间");
return resultMap;
}
} catch (ParseException e) {
e.printStackTrace();
}
//3. 存入MySQL
boolean result = iStockDao.insertLimitPolicy(policyInfo);
//如果没有执行成功,返回错误信息
if (!result) {
resultMap.put("result", false);
resultMap.put("msg", "数据库写入政策时失败!");
return resultMap;
}
String policy = JSON.toJSONString(policyInfo);
//4.1 商品政策写入Redis,并设置redis的有效期
stringRedisTemplate.opsForValue().set("LIMIT_POLICY_" + policyInfo.get("sku_id"), policy, diff, TimeUnit.SECONDS);
//4.2 从MySQL中将该商品信息取出写入redis
ArrayList<Map<String, Object>> list = iStockDao.getStock(policyInfo.get("sku_id").toString());
String sku = JSON.toJSONString(list.get(0));
stringRedisTemplate.opsForValue().set("SKU_" + policyInfo.get("sku_id").toString(), sku, diff, TimeUnit.SECONDS);
//5. 返回正常信息
resultMap.put("result", true);
resultMap.put("msg", "政策写入完毕!");
return resultMap;
}
7.Redis计数器,超过设置的值直接返回错误,不提供后续服务
if (stringRedisTemplate.opsForValue().increment("SKU_QUANTY_" + sku_id, 1) <= limitQuanty) {
@Override
public Map<String,Object> createOrder(String sku_id, String user_id){
Map<String,Object> resultMap = new HashMap<String,Object>();
//1.判断sku_id
if(sku_id==null || sku_id.equals("")){
resultMap.put("result",false);
resultMap.put("msg","无sku_id参数");
return resultMap;
}
//订单id取当前的时间
String order_id = String.valueOf((int)(Math.random()*10000+1))+String.valueOf((int)(Math.random()*10000+1))+String.valueOf((int)(Math.random()*10000+1))+String.valueOf((int)(Math.random()*10000+1));
// String order_id = UUID.randomUUID().toString().replace("-", "").substring(6);
//2.从Redis中取存入的活动政策limitpolicy
String key = "LIMIT_POLICY_"+sku_id;
String policy = stringRedisTemplate.opsForValue().get(key);
// String policy = stringRedisTemplate.opsForValue().get("LIMIT_POLICY_" + sku_id);
if(policy!=null && !policy.equals("")){
Map<String,Object> policyMap = JSONObject.parseObject(policy, Map.class);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String now = restTemplate.getForObject("http://leyou-time-server/getTime",String.class);
try {
Date end_time = simpleDateFormat.parse(policyMap.get("end_time").toString());
Date begin_time = simpleDateFormat.parse(policyMap.get("begin_time").toString());
Date now_time = simpleDateFormat.parse(now);
//3. 判断时间是否合法
if(begin_time.getTime()<=now_time.getTime() && now_time.getTime()<=end_time.getTime()) {
int limitQuanty = Integer.parseInt(policyMap.get("quanty").toString());
//4. Redis计数器
if (stringRedisTemplate.opsForValue().increment("SKU_QUANTY_" + sku_id, 1) <= limitQuanty) {
。。。。。。。。。。。
try {
amqpTemplate.convertAndSend("order_queue",order);
}catch (Exception e){
resultMap.put("result", false);
resultMap.put("msg", "写入队列异常!");
return resultMap;
}
}else {
//超出Redis计数器,直接踢回
//如果超出了计数器,返回商品已经售完了
resultMap.put("result", false);
resultMap.put("msg", "3亿9被踢回去了!");
return resultMap;
}
}else {
//结束时间大于当前时间,活动已过期
resultMap.put("result", false);
resultMap.put("msg", "活动已过期或时间还未到!");
return resultMap;
}
} catch (ParseException e) {
e.printStackTrace();
}
}else {
//policy为空
resultMap.put("result", false);
resultMap.put("msg", "Redis里面无LIMIT_POLICY!");
return resultMap;
}
//正常返回
resultMap.put("result",true);
resultMap.put("msg","数据正常返回");
resultMap.put("order_id",order_id);
return resultMap;
}
8. RabbitMQ队列的配置和使用
(1)写队列
①订单队列OrderQueue
@Component
public class OrderQueue {
@Autowired
private IOrderService iOrderService;
@RabbitListener(queues = "order_queue")
public void insertOrder(String msg) { //此处的msg就是订单信息
//1. 接受信息并输出
System.out.println("order_queue接收到信息:" + msg);
//2.调用写入订单的方法
Map orderInfo = JSONObject.parseObject(msg, Map.class);
Map<String, Object> resulthMap = new HashMap<String, Object>();
resulthMap = iOrderService.insertOrder(orderInfo);
//3.如果没写成功输出错误信息
if (!(Boolean) resulthMap.get("result")) {
System.out.println("order_queue处理信息失败! ");
}
System.out.println("order_queue处理信息成功 ");
}
}
②配置MQConfig,设置队列持久化
public class MQConfig {
@Bean
public Queue queueOrder() {
return new Queue("order_queue", true);
}
}
(2)使用队列
amqpTemplate.convertAndSend("order_queue", order);//交由队列order_queue处理insertOrder
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private AmqpTemplate amqpTemplate;
@Autowired
private IOrderDao iOrderDao;
@Override
public Map<String, Object> createOrder(String sku_id, String user_id) {
Map<String, Object> resultMap = new HashMap<String, Object>();
//2.从Redis中取存入的活动政策limitpolicy
String policy = stringRedisTemplate.opsForValue().get("LIMIT_POLICY_" + sku_id);
Map<String, Object> orderInfo = new HashMap<String, Object>();
orderInfo.put("order_id", order_id);
String order = JSON.toJSONString(orderInfo);
try {
amqpTemplate.convertAndSend("order_queue", order);
} catch (Exception e) {
resultMap.put("result", false);
resultMap.put("msg", "写入队列异常!");
return resultMap;
}
//正常返回
resultMap.put("result", true);
resultMap.put("msg", "数据正常返回");
resultMap.put("order_id", order_id);
return resultMap;
}
①②③④⑤