### Apache Airflow version

main (development)

### What happened

Currently `extra_links` is a cached property that returns a list without any order since set is used. Since we have 3 links per operator this order gets shuffled during webserver restarts as reported by users. It would be good to have this sorted so that the order is predictable. This is already done in extra_links Airflow API output

https://github.com/apache/airflow/blob/d7899ecfafb20cc58f8fb43e287d1c6778b8fa9f/airflow/models/abstractoperator.py#L470-L472

https://github.com/apache/airflow/blob/d7899ecfafb20cc58f8fb43e287d1c6778b8fa9f/airflow/api_connexion/endpoints/extra_link_endpoint.py#L75-L78

### What you think should happen instead

extra link order should be predictable

### How to reproduce

1. Create an operator with 3 or more extra links.
2. Render the links in UI. 
3. Restart the webserver and check the extra link order.

### Operating System

Ubuntu

### Versions of Apache Airflow Providers

_No response_

### Deployment

Other

### Deployment details

_No response_

### Anything else

_No response_

### Are you willing to submit PR?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)

"""Returns dictionary of all global extra links."""
        from airflow import plugins_manager
        plugins_manager.initialize_extra_operators_links_plugins()
        if plugins_manager.global_operator_extra_links is None:
            raise AirflowException("Can't load operators")
        return {link.name: link for link in plugins_manager.global_operator_extra_links}
    @cached_property
    def extra_links(self) -> list[str]:
        return list(set(self.operator_extra_link_dict).union(self.global_operator_extra_link_dict))
    def get_extra_links(self, ti: TaskInstance, link_name: str) -> str | None:
        """For an operator, gets the URLs that the ``extra_links`` entry points to.
        :meta private:
        :raise ValueError: The error message of a ValueError will be passed on through to
            the fronted to show up as a tooltip on the disabled link.
        :param ti: The TaskInstance for the URL being searched for.
        :param link_name: The name of the link we're looking for the URL for. Should be
            one of the options specified in ``extra_links``.
        """
        link: BaseOperatorLink | None = self.operator_extra_link_dict.get(link_name)
        if not link:
            link = self.global_operator_extra_link_dict.get(link_name)
            if not link:
                return None
        parameters = inspect.signature(link.get_link).parameters
        old_signature = all(name != "ti_key" for name, p in parameters.items() if p.kind != p.VAR_KEYWORD)
        if old_signature:
            return link.get_link(self.unmap(None), ti.dag_run.logical_date)  
        return link.get_link(self.unmap(None), ti_key=ti.key)
    @cache
    def get_parse_time_mapped_ti_count(self) -> int: