//explain检查索引使用情况 explain select * from `tb_order` where code='002';
索引失效的原因
同一条sql,只有入参不同而已。有的时候走的索引a,有的时候却走的索引b?这就是mysql会选错索引,必要时可以使用force index来强制查询sql走某个索引。
在java8之前可以通过实现Callable接口,获取线程返回结果。java8以后通过CompleteFuture类实现该功能。这里以CompleteFuture为例:
//定义线程池 @Configuration public class MyThreadConfig { @Bean public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) { return new ThreadPoolExecutor( pool.getCoreSize(), pool.getMaxSize(), pool.getKeepAliveTime(), TimeUnit.SECONDS, new LinkedBlockingDeque<>(100000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); } }
@Resource private ThreadPoolExecutor executor; public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException { final UserInfo userInfo = new UserInfo(); CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> { getRemoteUserAndFill(id, userInfo); return Boolean.TRUE; }, executor); CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> { getRemoteBonusAndFill(id, userInfo); return Boolean.TRUE; }, executor); CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> { getRemoteGrowthAndFill(id, userInfo); return Boolean.TRUE; }, executor); CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join(); userFuture.get(); bonusFuture.get(); growthFuture.get(); return userInfo; }
数据统一存储到一个地方,比如:redis
public synchronized doSave(String fileUrl) { mkdir(); uploadFile(fileUrl); sendMessage(fileUrl); }
这种直接在方法上加锁,锁的粒度有点粗。因为doSave方法中的上传文件和发消息方法,是不需要加锁的。只有创建目录方法,才需要加锁。
public void doSave(String path,String fileUrl) { synchronized(this) { if(!exists(path)) { mkdir(path); } } uploadFile(fileUrl); sendMessage(fileUrl); }
public void doSave(String path,String fileUrl) { if(this.tryLock()) { mkdir(path); } uploadFile(fileUrl); sendMessage(fileUrl); } private boolean tryLock() { try { String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime); if ("OK".equals(result)) { return true; } } finally{ unlock(lockKey,requestId); } return false; }
mysql数据库中主要有三种锁:
并发度越高,意味着接口性能越好。所以数据库锁的优化方向是:优先使用行锁,其次使用间隙锁,再其次使用表锁。
将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。
List<List<Long>> allIds = Lists.partition(ids,200); for(List<Long> batchIds:allIds) { List<User> users = remoteCallUser(batchIds); }
List<List<Long>> allIds = Lists.partition(ids,200); final List<User> result = Lists.newArrayList(); allIds.stream().forEach((batchIds) -> { CompletableFuture.supplyAsync(() -> { result.addAll(remoteCallUser(batchIds)); return Boolean.TRUE; }, executor); })
使用二级缓存,即基于内存的缓存。除了自己手写的内存缓存之后,目前使用比较多的内存缓存框架有:guava、Ehcache、caffine等。
以caffeine为例,它是spring官方推荐的。
第一步,添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.6.0</version> </dependency>
第二步,配置CacheManager,开启EnableCaching。
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager(){ CaffeineCacheManager cacheManager = new CaffeineCacheManager(); //Caffeine配置 Caffeine<Object, Object> caffeine = Caffeine.newBuilder() //最后一次写入后经过固定时间过期 .expireAfterWrite(10, TimeUnit.SECONDS) //缓存的最大条数 .maximumSize(1000); cacheManager.setCaffeine(caffeine); return cacheManager; } }
第三步,使用Cacheable注解获取数据
@Service public class CategoryService { @Cacheable(value = "category", key = "#categoryKey") public CategoryModel getCategory(String categoryKey) { String json = jedis.get(categoryKey); if(StringUtils.isNotEmpty(json)) { CategoryTree categoryTree = JsonUtil.toObject(json); return categoryTree; } return queryCategoryTreeFromDb(); } }
调用categoryService.getCategory()方法时,先从caffine缓存中获取数据,如果能够获取到数据,则直接返回该数据,不进入方法体。如果不能获取到数据,则再从redis中查一次数据。如果查询到了,则返回数据,并且放入caffine中。如果还是没有查到数据,则直接从数据库中获取到数据,然后放到caffine缓存中。
注:
该方案的性能更好,但有个缺点就是,如果数据更新了,不能及时刷新缓存
。此外,如果有多台服务器节点,可能存在各个节点上数据不一样的情况。