不想造轮子的程序员成为不了技术专家(一)

Handpc
发布于 2022-6-22 17:12
浏览
0收藏

 

小马哥,一个不是在造轮子,就是在造轮子路上的程序员,追求优雅编码。写过博客,参与过开源项目,热衷于基础架构。

  • CSDN 博客专家,掘金年度人气作者;
  • 龙台的技术笔记,公众号 1w+ 读者;
  • 开源 Hippo-4J 线程池框架,Github 2.4k+ star,码云 GVP 项目;
  • Apache ShardingSphere Contributor。

 

星球介绍 
做星球的初衷,希望通过商城项目,模拟出日常开发的不同难题,并给出合适的解决方案,帮助大家收获成长!

小马哥以为,星球还是要以 代码实战课 为中心,围绕以下五个点进行讲述:

 

1)DDD 开发模式
DDD 全称为(Domain-Driven Design,简称 DDD),领域驱动设计。

在互联网初期,对于一些简单的业务,只需要使用一个项目,编写多个业务逻辑就可以搞定,也就是最初的 单体项目阶段。

随着互联网的的发展,以及业务复杂度增长,分布式技术快速兴起,将单体项目根据业务拆分为多个服务,就到了 微服务时代。

但是,随着业务越来越复杂,当需要修改其中某项功能时,发现不太容易修改,因为改了这个功能后会 引起其它模块代码的级联反应。造成开发者业务开发周期增加,人力成本过高等问题。

DDD 模型可以很好的解决上述问题。所以,星球产出的商城系统会在 多个业务模块中使用 DDD 开发,梳理业务模型,帮助对 DDD 一知半解的开发者树立正确的开发思路。

 

2)商城业务
大部分同学在开发简单业务系统中,很难遇到有挑战性的技术难点,这也是导致技术进步缓慢的主要问题。

星球推出的 Mall4J 系统,参照商城类系统原型,推出 C 端用户、消息中台、订单、优惠券、支付 等业务模块,帮助大家梳理商城相关的业务;同时,也会在商城业务中使用到下述技术:

  • 分布式锁;
  • 分布式事务;
  • 搜索技术;
  • 微服务流量监控;
  • 分库分表等。


3)场景实战
当业务系统持续开发过程中,会遇到 各式各样的场景问题,小马哥根据工作中遇到的和帮助他人已解决的,总结了以下几点,并把解决方案和代码实战放在 Mall4J 这个系统里。

  • MySQL 百万级别数据,如何导出 Execl?
  • 线上 Java 应用触发 OOM,如何第一时间通知到开发者?
  • 如何解决雪花算法在集群环境下,生成 ID 重复问题?
  • 如何通过 MyBatis 查询千万数据,并保证内存不溢出?
  • MySQL 单表千万级数据,如何避免深分页引起的线上故障?
  • MySQL 数据库表快速初始化千万数据到 ElasticSearch?
  • ……


4)基础架构
每天面向业务编码,工作几年写的都是不变花样的 CRUD,开发者不知道如何对技术做提升?

相信这上面这段话是很多小伙伴的困扰,找不到一个好的方式 提升自己的基础架构能力。

为此,在 Mall4J 系统中,小马哥会带着大家造很多的“轮子”,包括不限于:规约、缓存、公共、分布式 ID、数据持久层、脱敏、日志、文档 API 等组件。

另外,之前和水友群沟通,发现大家对 Java Agent 概念不是很清楚,也没有在实际场景中使用过。

在基础架构部分,会根据实际的业务场景,写一个 中台业务个性化 Java Agent,它主要做两件事情:

  • 统计调用中台接口的来源系统,比如说中台消息发送接口被用户、订单、支付等几个系统调用;
  • 采集中台系统在不同的时间窗口下被调用的频率,可以精确到接口级别。


5)设计模式
为什么要学习设计模式?或者说设计模式有什么好处?

设计模式主要是为了 应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。避免做功能迭代时,牵一发而动全身。

同时,设计模式也是看各种底层框架的敲门砖,如果不会设计模式,点不了几个类就会被绕晕过去。

以上种种,无不彰显设计模式对于开发者的重要性。在 Mall4J 系统里,会根据不同的业务场景带大家掌握常用的设计模式,比如:单例、Builder、策略、模板方法、装饰器、动态代理等。

 

代码示例 
星球注重代码实战,这里放两段 Mall4J 中的真实代码,都是和大家工作中息息相关的技术。

 

1)分布式缓存封装
获取缓存时,防止缓存击穿、缓存穿透问题。

其中用到了分布式锁防止缓存击穿,并加上双重判断,最大可能性 减少流量请求数据库的次数;其次,使用布隆过滤器缓存空的结果,避免缓存穿透。

@Override
public <T> T safeGet(@NotBlank String key, Class<T> clazz, CacheLoader<T> cacheLoader, long timeout) {
    T result = get(key, clazz);
    if (!CacheUtil.isNullOrBlank(result)) {
        return result;
    }
    // 判断布隆过滤器是否存在,存在返回空
    if (cachePenetrationBloomFilter.contains(key)) {
        return null;
    }
    RLock lock = redissonClient.getLock(CacheUtil.buildKey("distributed_lock_lock_get", key));
    lock.lock();
    try {
        // 双重判定锁,减轻数据库访问压力
        if (CacheUtil.isNullOrBlank(result = get(key, clazz))) {
            // 通过 load 接口加载为空,触发缓存穿透条件,把 key 放入布隆过滤器
            if (CacheUtil.isNullOrBlank(result = loadAndSet(key, cacheLoader, timeout))) {
                cachePenetrationBloomFilter.add(key);
            }
        }
    } finally {
        lock.unlock();
    }
    return result;
}

 

2)封装策略模式
封装设计模式中的策略模式,规避不同业务定义多个选择器和策略接口。

定义抽象策略接口:

public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {
    
    /**
     * 执行策略标识
     */
    String mark();
    
    /**
     * 执行策略
     */
    default void execute(REQUEST requestParam) {
        
    }
    
    /**
     * 执行策略,带返回值
     */
    default RESPONSE executeResp(REQUEST requestParam) {
        return null;
    }
}

定义抽象策略选择器:

public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {
    
    /**
     * 执行策略集合
     */
    private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();
    
    /**
     * 根据 mark 查询具体策略
     */
    public AbstractExecuteStrategy choose(String mark) {
        return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));
    }
    
    /**
     * 根据 mark 查询具体策略并执行
     */
    public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        executeStrategy.execute(requestParam);
    }
    
    /**
     * 根据 mark 查询具体策略并执行,带返回结果
     */
    public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        return (RESPONSE) executeStrategy.executeResp(requestParam);
    }
    
    @Override
    public void onApplicationEvent(ApplicationInitializingEvent event) {
        Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);
        actual.forEach((beanName, bean) -> {
            AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());
            if (beanExist != null) {
                throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));
            }
            abstractExecuteStrategyMap.put(bean.mark(), bean);
        });
    }
}

 

3)项目结构

不想造轮子的程序员成为不了技术专家(一)-鸿蒙开发者社区

文章转自公众号:龙台的技术笔记

标签
已于2022-6-22 17:12:51修改
收藏
回复
举报
回复
    相关推荐