java并发编程笔记(十一)——高并发处理思路和手段
扩容
垂直扩容(纵向扩展):提高系统部件能力
水平扩容(横向扩容):增加更多系统成员来实现
缓存
缓存特征
命中率:命中数/(命中数+没有命中数)
一、影响因素
业务场景和业务需求
缓存的设计(粒度和策略)
缓存的容量和基础设施
二、缓存分类和应用场景
本地缓存:编程实现(成员变量,局部变量,静态变量)、Guava Cache
分布式缓存:Memcache、Redis
三、常用组件
Guava Cache public class GuavaCacheExample1 {
public static void main(String[] args) {
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(10) // 最多存放10个数据
.expireAfterWrite(10, TimeUnit.SECONDS) // 缓存10秒
.recordStats() // 开启记录状态数据功能
.build(new CacheLoader() {
@Override
public Integer load(String key) throws Exception {
return -1;
}
});
log.info("{}", cache.getIfPresent("key1")); // null
cache.put("key1", 1);
log.info("{}", cache.getIfPresent("key1")); // 1
cache.invalidate("key1");
log.info("{}", cache.getIfPresent("key1")); // null
try {
log.info("{}", cache.get("key2")); // -1
cache.put("key2", 2);
log.info("{}", cache.get("key2")); // 2
log.info("{}", cache.size()); // 1
for (int i = 3; i < 13; i++) {
cache.put("key" + i, i);
}
log.info("{}", cache.size()); // 10
log.info("{}", cache.getIfPresent("key2")); // null
Thread.sleep(11000);
log.info("{}", cache.get("key5")); // -1
log.info("{},{}", cache.stats().hitCount(), cache.stats().missCount());
log.info("{},{}", cache.stats().hitRate(), cache.stats().missRate());
} catch (Exception e) {
log.error("cache exception", e);
}
}
}
Memcache
Redis
**最大元素(空间):缓存大小 **
清空策略:FIFO、LFO、LRU、过期时间,随机等
FIFO: 先进先出策略
LFO:最少使用策略
LRU:最近最少使用策略
高并发场景下缓存常见问题
缓存一致性
造成这种问题的场景:
更新数据库成功--更新缓存失败--数据不一致
更新缓存成功--更新数据库失败--数据不一致
更新数据库成功--淘汰缓存失败--数据不一致
淘汰缓存成功--更新数据库失败--查询缓存miss
缓存并发问题
同时大量请求访问缓存未命中,然后大量请求同时访问数据库造成数据库压力增大,解决办法是加锁,避免同时执行
缓存穿透问题
某个查询结果为空,导致缓存为存储,因此大量请求会去查询数据库,造成对数据库的压力大增
解决办法:
1、缓存空对象,缓存时间要短,适合命中率不高,但是需要频繁修改的数据
2、进行单独过滤处理,对所有可能结果为空的请求进行统一存放,并且对请求进行拦截,避免请求到数据库对数据库造成大量压力
缓存雪崩现象
导致原因:缓存并发,缓存穿透等
解决办法:过期时间设置随机数,避免同时失效10
消息队列
特性
业务无关:只做消息分发
FIFO:先投递先到达
容灾:节点的动态增加和消息的持久化
性能:吞吐量提升,系统内部通信效率提高
为什么需要消息队列
【生产和消费】的速度或稳定性等因素不一致
消息队列的好处
业务解耦
最终一致性
广播
错峰和流控
举例
Kafka
RabbitMQ
应用拆分
拆分原则
业务优先
循序渐进
兼顾技术:重构、分层
可靠测试
需要考虑的问题
应用之间的通信:RPC(dubbo等)、消息队列
应用之间数据数据库设计:每个应用都有独立的数据库
避免事务操作跨应用
通信工具
dubbo
springCloud
应用限流
常用限流算法
计数器法
滑动窗口法
漏桶算法
令牌桶算法(Guava RateLimit) @Slf4j
public class RateLimiterExample1 {
private static RateLimiter rateLimiter = RateLimiter.create(5);
public static void main(String[] args) throws Exception {
for (int index = 0; index < 100; index++) {
if (rateLimiter.tryAcquire(190, TimeUnit.MILLISECONDS)) {
handle(index);
}
}
}
private static void handle(int i) {
log.info("{}", i);
}
} @Slf4j
public class RateLimiterExample2 {
private static RateLimiter rateLimiter = RateLimiter.create(5);
public static void main(String[] args) throws Exception {
for (int index = 0; index < 100; index++) {
rateLimiter.acquire();
handle(index);
}
}
private static void handle(int i) {
log.info("{}", i);
}
}
Guava RateLimiter是单机版的限流
如果是分布式可以采用 分布式限流:
可以采用Redis作为中间组件,使用Redis的incrby key num 方法
服务降级与服务熔断
服务降级
自动降级:超时、失败次数、故障、限流
人工降级:秒杀、双11大促
服务熔断
总结
共性:目的、最终表现、粒度、自治
区别:
触发原因、管理目标层次、实现方式
服务降级要考虑的问题
核心服务、非核心服务
是否支持降级、降级策略
业务放通场景,策略
Hystrix
在通过第三方客户端访问(通常是通过网络)依赖服务出现高延迟或者失败时,为系统提供保护和控制。
在分布式系统中防止级联失败
快速失败(fail fast)同时能快速恢复
提供失败回退(Fallback)和优雅的服务降级机制
提供近实时的监控、报警和运维控制手段
数据库切库、分库、分表
数据库瓶颈
单个库数据量太大(1T~2T):多个库
单个数据库服务器压力过大、读写瓶颈:多个库
单个表数据量过大:分表
数据库切库
切库的基础及实际运用:读写分离
自定义注解完成数据库切库-代码实现
支持多数据源、分库
数据库支持多个数据源-代码实现
数据库分表
什么时候考虑分表
横向(水平)分表和纵向(垂直)分表
数据库分表:mybatis分表插件shardbatis2.0
高可用的一些手段
任务调度系统分布式:elastic-job+zookeeper
主备切换:apache curator + zookeeper分布式锁实现
监控报警机制