You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Laravel中无超时/内存问题地从AD LDAP获取全部用户

Laravel 10 + AdLdap2 批量获取AD用户遇到的问题

我正在开发一个Laravel 10应用,用AdLdap2包和PHP 8的LDAP函数对接Active Directory(AD),但获取2万+用户时遇到了瓶颈。

已尝试的两种方法

方法1:使用AdLdap2包

private function handleAdLdapUsersEx($provider): void
{
    try {
        $pageSize = 200;

        if ($this->searchBase->filter) {
            $provider->where($this->searchBase->filter);
        }

        $pagedUsers = $provider->select('distinguishedname', 'givenname', 'name', 'objectguid', 'samaccountname', 'userprincipalname', 'mail')
            ->whereEnabled()
            ->limit($pageSize)
            ->paginate($pageSize, $this->currentPage);
        
        dump($pagedUsers->count());
        
        if ($pagedUsers->count() > 0) {
            collect($pagedUsers->getResults())->chunk(100)->each(function ($chunkedUsers) {
                $this->handleBulk($chunkedUsers);
                unset($chunkedUsers);
                gc_collect_cycles();
            });

            if ($pagedUsers->count() === $pageSize) {
                ImportUsersJob::dispatch($this->searchBase, $this->ldapConnector, $this->ldapConfig, $this->currentPage + 1);
            }
        }
    } catch (\Exception $e) {
        dd($e->getMessage());
    }
}

设置了分页限制,但实际还是一次性拉取所有记录导致超时。虽然把public $timeout = 600;可以解决问题,但不想硬编码超时时间。

方法2:使用PHP原生LDAP函数

private function handleAdLdapUsers($provider, $ldapConnector): void
{
    try {
        $usersFetched = 0;
        $lastUserEntry = null;

        $ldapConnection = ldap_connect($provider->getConnection()->getHost());
        ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_bind($ldapConnection, $ldapConnector->getUsernames(), $ldapConnector->getPassword());

        $filter = !empty($this->searchBase->filter) ? $this->searchBase->filter : "(objectClass=*)";

        $result = @ldap_search($ldapConnection, $provider->getDn(), $filter, [], 0, 1);
        $firstEntry = ldap_first_entry($ldapConnection, $result);

        while ($firstEntry) {
            $attributes = ldap_get_attributes($ldapConnection, $firstEntry);

            $users = $provider->select('distinguishedname', 'givenname', 'name', 'objectguid', 'samaccountname', 'userprincipalname', 'mail', 'usncreated')
                ->whereEnabled()
                ->get($attributes);

            if ($users->count() > 0) {
                $this->handleBulk($users);
                $usersFetched = $users->count();
            } else {
                break;
            }

            $lastUserEntry = ldap_next_entry($ldapConnection, $firstEntry);
            $firstEntry = $lastUserEntry;
        }

        ldap_unbind($ldapConnection);
    } catch (\Exception $e) {
        dd($e->getMessage());
    }
}

出现了“Sizelimit exceeded”警告,只能获取1000条记录。虽然用@ldap_search屏蔽了警告,但需要完整获取甚至超过5万的用户,同时避免大小限制和内存问题。

待解决的问题

  1. 如何修改查询或分页逻辑,高效获取全部用户?
  2. Laravel中查询LDAP大数据集有哪些最佳实践?
  3. 能否通过配置LDAP服务器放宽默认的大小限制?

解决方案

1. 修复分页逻辑,实现真正的LDAP分页

AdLdap2的paginate方法依赖LDAP的分页控制扩展(Paged Results Control),当前代码可能未正确启用该功能,以下是修复方案:

修改AdLdap2配置

在config/ldap.php的连接配置中开启分页支持:

'connections' => [
    'default' => [
        // ...其他配置
        'settings' => [
            // ...其他设置
            'enable_paging' => true, // 开启分页支持
            'page_size' => 200, // 默认分页大小
        ],
    ],
],

调整AdLdap2代码逻辑

利用AdLdap2自带的分页迭代器,自动遍历所有分页:

private function handleAdLdapUsersEx($provider): void
{
    try {
        $query = $provider->select('distinguishedname', 'givenname', 'name', 'objectguid', 'samaccountname', 'userprincipalname', 'mail')
            ->whereEnabled();

        if ($this->searchBase->filter) {
            $query->where($this->searchBase->filter);
        }

        // 自动处理所有分页,逐页拉取数据
        $pagedUsers = $query->paginate(200);

        foreach ($pagedUsers as $page) {
            collect($page)->chunk(100)->each(function ($chunk) {
                $this->handleBulk($chunk);
                gc_collect_cycles(); // 及时释放内存
            });
        }
    } catch (\Exception $e) {
        report($e);
    }
}

这种方式不会一次性加载所有记录,从根源避免超时和内存溢出。

原生LDAP函数实现分页

如果用原生PHP LDAP,需手动启用分页控制绕过1000条限制:

private function handleAdLdapUsers($provider, $ldapConnector): void
{
    try {
        $ldapConnection = ldap_connect($provider->getConnection()->getHost());
        ldap_set_option($ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($ldapConnection, LDAP_OPT_REFERRALS, 0); // 禁用引用,减少无效数据
        ldap_bind($ldapConnection, $ldapConnector->getUsernames(), $ldapConnector->getPassword());

        $pageSize = 200;
        $cookie = '';
        // 直接过滤启用的用户,替代whereEnabled()
        $filter = !empty($this->searchBase->filter) ? $this->searchBase->filter : "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";

        do {
            ldap_control_paged_result($ldapConnection, $pageSize, true, $cookie);
            $result = ldap_search($ldapConnection, $provider->getDn(), $filter, [
                'distinguishedname', 'givenname', 'name', 'objectguid', 'samaccountname', 'userprincipalname', 'mail'
            ]);
            
            $entries = ldap_get_entries($ldapConnection, $result);
            
            if ($entries['count'] > 0) {
                $this->handleBulk(collect(array_slice($entries, 1))); // 跳过count字段
            }

            // 获取下一页的cookie
            ldap_control_paged_result_response($ldapConnection, $result, $cookie);
        } while ($cookie !== null && $cookie !== '');

        ldap_unbind($ldapConnection);
    } catch (\Exception $e) {
        report($e);
    }
}

2. Laravel查询LDAP大数据集的最佳实践

  • 拆分队列任务:把导入拆成单页任务,用Laravel队列异步处理,避免阻塞主进程,完成一页后自动调度下一页任务。
  • 精准选择属性:不要用select('*'),明确指定需要的字段,减少数据传输量和内存占用。
  • 主动释放内存:每批数据处理完后,用unset()销毁变量,调用gc_collect_cycles()强制垃圾回收。
  • 动态设置超时:在队列任务中根据每页数据量动态计算超时,而非硬编码:
    public $timeout;
    
    public function __construct() {
        $this->timeout = ceil(200 / 50) * 60; // 每50条用户分配60秒超时
    }
    
  • 禁用LDAP引用:设置LDAP_OPT_REFERRALS为0,避免AD返回额外的引用数据,减少网络开销。

3. 配置LDAP服务器放宽大小限制

可以修改AD服务器的默认查询限制,但需要AD管理员权限:

  • 全局查询限制:在AD服务器的Active Directory Users and Computers中,右键域名→属性→“组策略”→编辑→计算机配置→管理模板→系统→LDAP客户端→“LDAP服务器查询大小限制”,设置目标数值(如50000)。
  • 单个账号限制:给服务账号单独设置查询限制,在AD用户属性→“账户”→“高级”→“查询限制”中调整数值。

注意:放宽服务器端限制会增加AD负载,建议优先用分页方案解决,仅在必要时调整服务器配置。


内容的提问来源于stack exchange,提问作者Fawwash Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 21:58:09