如果产品服务的业务量不是很大,业务又不是特别繁杂,功能模块的拆分颗粒度就不需要太细,也就没必要上微服务。因为微服务的运维治理复杂度及服务器资源开销代价是难以想象的。
本编简述下如何使用Dubbo+Zookeeper即可实现传统的分布式服务,并且支持高并发高可用场景下的动态扩容。
有关Dubbo的介绍,就无需多言了,它是一款高性能、轻量级的开源Java RPC框架(Remote Procedure Call Protocol 远程过程调用协议),它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
准备工作:
-
搭建springboot脚手架并成功运行,可参考历史分享springboot+mybatis
-
启动Zookeeper服务(作为Dubbo的服务注册协调中心)(搭建配置ZK服务,后续会在运维章节另行讲述)
1. maven添加Dubbo及ZK依赖
<properties>
<zk-curator.version>4.0.1</zk-curator.version>
</properties>
<dependencies>
<!--dubbo-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!--zookeeper-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--zk-lock-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${zk-curator.version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${zk-curator.version}</version>
</dependency>
</dependencies>
2.Dubbo配置
2.1 yml
#dubbo
dubbo:
application:
name: demo-user
registry:
address: zookeeper://192.168.2.7:2181?backup=192.168.2.8:2181,192.168.2.9:2181
file: /dubbo/demo-user/cache
protocol: zookeeper
check: false
protocol:
name: dubbo
port: 28102
scan:
base-packages: com.demo.user.service.dubbo
consumer:
check: false
timeout: 10000
retries: 0
#ZK lock
lock:
zk-servers: 192.168.2.7:2181,192.168.2.8:2181,192.168.2.9:2181
2.2 开启dubbo服务注解扫描
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubbo
@SpringBootApplication
public class DemoUserApplication {
public static void main(String[] args) {
SpringApplication.run(DemoUserApplication.class, args);
}
}
3.Dubbo调用
3.1 dubbo service provider
import lombok.extern.slf4j.Slf4j;
import com.alibaba.dubbo.config.annotation.Service;
@Slf4j
@Service // 注意该@Service注解是alibaba包下的,不要错引用了Spring包下的
public class UserDubboServiceImpl implements UserDubboService {
}
3.2 dubbo service consumer
import com.alibaba.dubbo.config.annotation.Reference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderServiceImpl extends BaseServiceImpl<Order>
implements OrderService {
@Reference // 引用注入dubbo服务service bean
private UserDubboService userDubboService;
}
4.ZK分布式锁
4.1 zk lock 配置
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁:
* 注意该锁不能重入,即不可有嵌套锁
*/
@Slf4j@Configuration // 注意该组件需被springboot扫描注册成bean
public class DistributedLock implements InitializingBean {
private static CuratorFramework curator = null;
private static ThreadLocal<InterProcessSemaphoreMutex> localLock = new ThreadLocal<>();
@Value("${lock.zk-servers}")
private String zkServers;
private static final String basePath = "/lock/"; /** * bean添加到spring容器后,在完成bean实例化后才会执行该方法 */
@Override public void afterPropertiesSet() {
if (curator == null) {
curator = CuratorFrameworkFactory.builder().sessionTimeoutMs(30000).connectionTimeoutMs(30000)
.retryPolicy(new ExponentialBackoffRetry(1000, Integer.MAX_VALUE)).connectString(zkServers).build();
curator.getConnectionStateListenable().addListener(new ZKListener());
curator.start();
}
}
/**
* 不等待,遇锁直接退出
*
* @param key
* @return
*/
public static boolean acquire(String key) {
try {
InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(DistributedLock.curator, basePath + key);
lock.acquire();
localLock.set(lock);
} catch (Exception e) {
log.warn("DistributedLock acquire error", e);
return false;
}
return true;
}
/**
* 排队等待
*
* @param key
* @param timeout
* @return
*/
public static boolean acquire(String key, long timeout) {
try {
InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(DistributedLock.curator, basePath + key);
lock.acquire(timeout, TimeUnit.SECONDS);
localLock.set(lock);
} catch (Exception e) {
log.warn("DistributedLock acquire error", e);
return false;
}
return true;
}
/**
* 排队等待
*
* @param key
* @param timeout
* @return
*/
public static boolean acquire(String key, long timeout, TimeUnit unit) {
try {
InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(DistributedLock.curator, basePath + key);
lock.acquire(timeout, unit);
localLock.set(lock);
} catch (Exception e) {
log.warn("DistributedLock acquire error", e);
return false;
}
return true;
}
/**
* 释放锁
*/
public static void release() {
try {
InterProcessSemaphoreMutex lock = localLock.get();
if (lock != null && lock.isAcquiredInThisProcess()) {
lock.release();
}
} catch (Exception e) {
log.warn("DistributedLock release error", e);
}
}
/**
* zk监听器
*/
public class ZKListener implements ConnectionStateListener {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
if (ConnectionState.LOST.equals(state)) {
// 连接丢失
log.warn("==ZK LOCK== DistributedLock lost session with zookeeper");
} else if (ConnectionState.CONNECTED.equals(state)) {
// 连接新建
log.warn("==ZK LOCK== DistributedLock connected with zookeeper");
} else if (ConnectionState.RECONNECTED.equals(state)) {
// 重新连接
log.warn("==ZK LOCK== DistributedLock reconnected with zookeeper");
}
}
}
/**
* 分布式锁业务类型
*/
public enum LockType {
PRODUCT_INVENTORY
}
}
4.2 zk lock 使用
// 加锁,
String lockKey = DistributedLock.LockType.PRODUCT_INVENTORY + "/" + productId;
try{
if(DistributedLock.acquire(lockKey, 3)){
// todo something 抢购排队下单,扣减商品库存
}
}catch(Exception e){
log.error("userId = {}, couponId = {}, 下单失败:{}", userId, productId, e.getMessage());
throw new Exception("下单失败", e);
}finally{
DistributedLock.release();
}
// service 方法中抛出异常,回滚事务
throw new CommonException("下单失败");
5.CAP理论
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项,也即不可能三角CAP理论。
既然是分布式服务,自然需要具备分区容错性。除此之外,用户更多的是需要服务的可用性,至于一致性方面,目前常用的折中解决方案是,选择最终一致性。
以上关于后端服务常用的框架技术及一些相关中间件都已整理完毕,后续会更新基于SpringCloud Alibaba的微服务有关章节。